From 7a9fb5c8a5a5f2bfdf6e5134cc1d9c9262fc21da Mon Sep 17 00:00:00 2001 From: Steven Pritchard Date: Mon, 16 Sep 2024 13:39:51 -0500 Subject: [PATCH] [puppetsync] Clean up for linters Clean up files distributed by puppetsync for various linters. Also fix a quoting issue in create-github-release action. --- .gitattributes | 11 + .../add_new_issue_to_triage_project.yml | 40 ++ .github/workflows/pr_tests.yml | 142 +++++++ .github/workflows/release_rpms.yml | 353 +++++++++++++++++ .github/workflows/tag_deploy.yml | 196 +++++++++ .github/workflows/validate_tokens.yml | 68 ++++ .gitignore | 67 ++-- .gitlab-ci.yml | 375 ++++++++++++++++++ .pdkignore | 57 +++ .puppet-lint.rc | 15 + .rspec | 3 + Gemfile | 60 ++- spec/spec_helper.rb | 169 ++++++++ 13 files changed, 1503 insertions(+), 53 deletions(-) create mode 100644 .gitattributes create mode 100644 .github/workflows/add_new_issue_to_triage_project.yml create mode 100644 .github/workflows/pr_tests.yml create mode 100644 .github/workflows/release_rpms.yml create mode 100644 .github/workflows/tag_deploy.yml create mode 100644 .github/workflows/validate_tokens.yml create mode 100644 .gitlab-ci.yml create mode 100644 .pdkignore create mode 100644 .puppet-lint.rc create mode 100644 .rspec create mode 100644 spec/spec_helper.rb diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..0275e10 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +# ------------------------------------------------------------------------------ +# NOTICE: **This file is maintained with puppetsync** +# +# This file is automatically updated as part of a puppet module baseline. +# The next baseline sync will overwrite any local changes made to this file. +# ------------------------------------------------------------------------------ +*.erb eol=lf +*.pp eol=lf +*.sh eol=lf +*.epp eol=lf +*.rb eol=lf diff --git a/.github/workflows/add_new_issue_to_triage_project.yml b/.github/workflows/add_new_issue_to_triage_project.yml new file mode 100644 index 0000000..f3c5b84 --- /dev/null +++ b/.github/workflows/add_new_issue_to_triage_project.yml @@ -0,0 +1,40 @@ +# Add new issues to triage project board (https://github.com/orgs/simp/projects/11) +# ------------------------------------------------------------------------------ +# +# NOTICE: **This file is maintained with puppetsync** +# +# This file is updated automatically as part of a puppet module baseline. +# +# The next baseline sync will overwrite any local changes to this file! +# +# ============================================================================== +# This pipeline uses the following GitHub Action Secrets: +# +# GitHub Secret variable Notes +# ------------------------------- --------------------------------------- +# AUTO_TRIAGE_TOKEN Token with appropriate permissions +# +# ------------------------------------------------------------------------------ +# +# +--- +name: Add new issues to triage project + +'on': + issues: + types: + - opened + - reopened + pull_request_target: + types: + - opened + +jobs: + add-to-project: + name: Add issue to project + runs-on: ubuntu-latest + steps: + - uses: actions/add-to-project@v0.5.0 + with: + project-url: https://github.com/orgs/simp/projects/11 + github-token: ${{ secrets.AUTO_TRIAGE_TOKEN }} diff --git a/.github/workflows/pr_tests.yml b/.github/workflows/pr_tests.yml new file mode 100644 index 0000000..9c0e683 --- /dev/null +++ b/.github/workflows/pr_tests.yml @@ -0,0 +1,142 @@ +# Run Puppet checks and test matrix on Pull Requests +# ------------------------------------------------------------------------------ +# NOTICE: **This file is maintained with puppetsync** +# +# This file is updated automatically as part of a puppet module baseline. +# +# The next baseline sync will overwrite any local changes to this file! +# +# ============================================================================== +# +# The testing matrix considers ruby/puppet versions supported by SIMP and PE: +# ------------------------------------------------------------------------------ +# Release Puppet Ruby EOL +# PE 2021.Y 7.x 2.7 2025-02 (LTS) +# PE 2023.Y 8.x 3.2 Biannual updates +# +# https://puppet.com/docs/pe/latest/component_versions_in_recent_pe_releases.html +# https://puppet.com/misc/puppet-enterprise-lifecycle +# ============================================================================== +# +# https://docs.github.com/en/actions/reference/events-that-trigger-workflows +# +--- +name: PR Tests +'on': + pull_request: + types: [opened, reopened, synchronize] + +env: + PUPPET_VERSION: '~> 8' + +jobs: + puppet-syntax: + name: 'Puppet Syntax' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: "Install Ruby ${{matrix.puppet.ruby_version}}" + uses: ruby/setup-ruby@v1 # ruby/setup-ruby@ec106b438a1ff6ff109590de34ddc62c540232e0 + with: + ruby-version: 3.2 + bundler-cache: true + - run: "bundle exec rake syntax" + + puppet-style: + name: 'Puppet Style' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: "Install Ruby ${{matrix.puppet.ruby_version}}" + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.2 + bundler-cache: true + - run: "bundle exec rake lint" + - run: "bundle exec rake metadata_lint" + + ruby-style: + if: false # TODO Modules will need: rubocop in Gemfile, .rubocop.yml + name: 'Ruby Style (experimental)' + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v3 + - name: "Install Ruby ${{matrix.puppet.ruby_version}}" + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.2 + bundler-cache: true + - run: | + bundle show + bundle exec rake rubocop + + file-checks: + name: 'File checks' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: 'Install Ruby 3.2' + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.2 + bundler-cache: true + - run: bundle exec rake check:dot_underscore + - run: bundle exec rake check:test_file + + releng-checks: + name: 'RELENG checks' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: 'Install Ruby ${{matrix.puppet.ruby_version}}' + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.2 + bundler-cache: true + - name: 'Tags and changelogs' + run: | + bundle exec rake pkg:check_version + bundle exec rake pkg:compare_latest_tag[,true] + bundle exec rake pkg:create_tag_changelog + - name: 'Test-build the Puppet module' + run: 'bundle exec pdk build --force' + + spec-tests: + name: 'Puppet Spec' + needs: [puppet-syntax] + runs-on: ubuntu-latest + strategy: + matrix: + puppet: + - label: 'Puppet 7.x [SIMP 6.6/PE 2021.7]' + puppet_version: '~> 7.0' + ruby_version: '2.7' + experimental: false + - label: 'Puppet 8.x' + puppet_version: '~> 8.0' + ruby_version: '3.2' + experimental: false + fail-fast: false + env: + PUPPET_VERSION: ${{matrix.puppet.puppet_version}} + steps: + - uses: actions/checkout@v3 + - name: 'Install Ruby ${{matrix.puppet.ruby_version}}' + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{matrix.puppet.ruby_version}} + bundler-cache: true + - run: 'command -v rpm || if command -v apt-get; then sudo apt-get update; sudo apt-get install -y rpm; fi ||:' + - run: 'bundle exec rake spec' + continue-on-error: ${{matrix.puppet.experimental}} + +# dump_contexts: +# name: 'Examine Context contents' +# runs-on: ubuntu-latest +# steps: +# - name: Dump contexts +# env: +# GITHUB_CONTEXT: ${{ toJson(github) }} +# run: echo "$GITHUB_CONTEXT" +# diff --git a/.github/workflows/release_rpms.yml b/.github/workflows/release_rpms.yml new file mode 100644 index 0000000..04f1ef5 --- /dev/null +++ b/.github/workflows/release_rpms.yml @@ -0,0 +1,353 @@ +# Manual action to build, sign, and attach a release's RPMs +# ------------------------------------------------------------------------------ +# +# NOTICE: **This file is maintained with puppetsync** +# +# This file is updated automatically as part of a puppet module baseline. +# +# The next baseline sync will overwrite any local changes to this file! +# +# ============================================================================== +# This pipeline uses the following GitHub Action Secrets: +# +# GitHub Secret variable Notes +# ------------------------------- --------------------------------------- +# SIMP_CORE_REF_FOR_BUILDING_RPMS simp-core ref (tag) to use to build +# RPMs with `rake pkg:single` against +# `build/rpms/dependencies.yaml` +# SIMP_DEV_GPG_SIGNING_KEY GPG signing key's secret key +# SIMP_DEV_GPG_SIGNING_KEY_ID User ID (name) of signing key +# SIMP_DEV_GPG_SIGNING_KEY_PASSPHRASE Passphrase to use GPG signing key +# +# ------------------------------------------------------------------------------ +# +# * This is a workflow_dispatch action, which can be triggered manually or from +# other workflows/API. +# +# * If triggered by another workflow, it will be necessary to provide a GitHub +# access token via the the `target_repo_token` parameter +# +# +--- +name: 'RELENG: Build + attach RPMs to GitHub Release' + +'on': + workflow_dispatch: + inputs: + release_tag: + description: "Release tag" + required: true + clobber: + description: "Clobber identical assets?" + required: false + default: 'yes' + clean: + description: "Wipe all release assets first?" + required: false + default: 'no' + autocreate_release: + # A GitHub release is needed to upload artifacts to, and some repos + # (e.g., forked mirrors) only have tags. + description: "Create release if missing? (tag must exist)" + required: false + default: 'yes' + build_container_os: + description: "Build container OS" + required: true + default: 'centos8' + target_repo: + description: "Target repo (instead of this one)" + required: false + # WARNING: To avoid exposing secrets in the log, only use this token with + # action/script's `github-token` parameter, NEVER in `env:` vars + target_repo_token: + description: "API token for uploading to target repo" + required: false + path_to_build: + # Example: simp-core builds pupmod from . and simp* from src/assets/simp + description: "Subpath to alternative RPM project" + required: false + dry_run: + description: "Dry run (Test-build RPMs)" + required: false + default: 'no' + # verbose: + # description: 'Verbose RPM builds when "yes"' + # required: false + # default: 'no' + rebuild_number: + description: 'If this is an RPM rebuild, put the number of the rebuild here' + required: false + default: '' + + +env: + TARGET_REPO: ${{ (github.event.inputs.target_repo != null && format('{0}/{1}', github.repository_owner, github.event.inputs.target_repo)) || github.repository }} + RELEASE_TAG: ${{ github.event.inputs.release_tag }} + +jobs: + create-and-attach-rpms-to-github-release: + name: > + Build and attach RPMs to Release: + ${{ (github.event.inputs.target_repo != null && format('{0}/{1}', github.repository_owner, github.event.inputs.target_repo)) || github.repository }} + ${{ github.event.inputs.release_tag }} + (build os: ${{ github.event.inputs.build_container_os }}) + runs-on: ubuntu-20.04 + steps: + - name: "Validate inputs" + id: validate-inputs + run: | + if ! [[ "$TARGET_REPO" =~ ^[a-z0-9][a-z0-9-]+/[a-z0-9][a-z0-9_-]+$ ]]; then + printf '::error ::Target repository name has invalid format: %s\n' "$TARGET_REPO" + exit 88 + fi + + if [[ "$RELEASE_TAG" =~ ^(simp-|v)?([0-9]+\.[0-9]+\.[0-9]+)(-(rc|RC|[Aa]lpha|[Bb]eta|pre|post)?([0-9]+)?)?$ ]]; then + if [ -n "${BASH_REMATCH[5]}" ]; then + echo "{prebuild_number}={${BASH_REMATCH[5]#-}}" >> $GITHUB_OUTPUT + fi + if [ -n "${BASH_REMATCH[3]}" ]; then + echo "{prebuild_suffix}={${BASH_REMATCH[3]#-}}" >> $GITHUB_OUTPUT + fi + if [ -n "${BASH_REMATCH[2]}" ]; then + echo "{build_semver}={${BASH_REMATCH[2]}}" >> $GITHUB_OUTPUT + fi + else + printf '::error ::Release Tag format is not SemVer, X.Y.Z-R, X.Y.Z-: "%s"\n' "$RELEASE_TAG" + exit 88 + fi + + - name: > + Query info for ${{ env.TARGET_REPO }} + release ${{ github.event.inputs.release_tag }} ${{ steps.validate-inputs.outputs.prebuild_suffix }} + build os ${{ github.event.inputs.build_container_os }} + (autocreate_release = '${{ github.event.inputs.autocreate_release }}') + id: release-api + env: + AUTOCREATE_RELEASE: ${{ github.event.inputs.autocreate_release }} + PREBUILD_TAG: ${{ steps.validate-inputs.outputs.prebuild_suffix }} + uses: actions/github-script@v6 + with: + github-token: ${{ github.event.inputs.target_repo_token || secrets.GITHUB_TOKEN }} + script: | + const [owner, repo] = process.env.TARGET_REPO.split('/') + const tag = process.env.RELEASE_TAG + const autocreate_release = (process.env.AUTOCREATE_RELEASE == 'yes') + const owner_data = { owner: owner, repo: repo } + const release_data = Object.assign( {tag: tag}, owner_data ) + const prerelease = process.env.PREBUILD_TAG ? true : false + const create_release_data = Object.assign( {tag_name: tag, prerelease: prerelease}, owner_data ) + const tag_data = Object.assign( {ref: `tags/${tag}`}, owner_data ) + + function id_from_release(data) { + console.log( ` >> Release for ${owner}/${repo}, tag ${tag}` ) + console.log( ` >>>> release_id: ${data.id}` ) + return data.id + } + + function throw_error_unless_should_autocreate_release(err){ + if (!( err.name == 'HttpError' && err.status == 404 && autocreate_release )){ + core.error(`Error finding release for tag ${tag}: ${err.name}`) + throw err + } + } + + async function autocreate_release_if_appropriate(err){ + throw_error_unless_should_autocreate_release(err) + core.warning(`Can't find release for tag ${tag} and tag exists, auto-creating release`) + + return await github.request( 'GET /repos/{owner}/{repo}/git/matching-refs/{ref}', tag_data ).then ( + result => { + // Must already have a tag + if (result.data.length == 0) { throw `Can't find tag ${tag} in repo ${owner}/${repo}` } + return result + } + ).then( + async result => { + return await github.request( 'POST /repos/{owner}/{repo}/releases', create_release_data).then( + result=>{ + release_id = id_from_release(result.data) + console.log(` ++ created auto release ${release_id}` ) + return release_id + }, + post_err =>{ + core.error('Error auto-creating release') + throw post_err + } + ) + } + ) + } + + await github.request('GET /repos/{owner}/{repo}/releases/tags/{tag}', release_data ).then( + async result => { return await id_from_release(result.data) }, + async err => { return await autocreate_release_if_appropriate(err) } + ).then( + release_id => { + if (!release_id){ + throw `Could not get release for ${tag} for repo ${owner}:${repo}` + } + console.log( ` **** release_id: ${release_id}` ) + core.setOutput('id', release_id) + }, + err => { throw err } + ) + + - name: Checkout code + uses: actions/checkout@v3 + with: + repository: ${{ env.TARGET_REPO }} + ref: ${{ env.RELEASE_TAG }} + clean: true + fetch-depth: 0 + + - name: 'Customize RPM Release tag via build/rpm_metadata/release (pre-release only)' + if: steps.validate-inputs.outputs.prebuild_suffix + env: + BUILD_SEMVER: ${{ steps.validate-inputs.outputs.build_semver }} + PREBUILD_TAG: ${{ steps.validate-inputs.outputs.prebuild_suffix }} + PREBUILD_NUMBER: ${{ steps.validate-inputs.outputs.prebuild_number }} + # Note: To accomodate the capabilities of EL7's version of RPM, the + # release number is formatted according to the Fedora Packaging + # Guidelines' "Traditional versioning" conventions: + # + # - https://fedoraproject.org/en-US/packaging-guidelines/Versioning/ + # - https://fedoraproject.org/wiki/Package_Versioning_Examples + # + run: | + mkdir -p build/rpm_metadata + # Special case for simp-doc's unique data format in /release + if [[ "$TARGET_REPO" =~ ^simp\/simp-doc$ ]]; then + echo "version: $BUILD_SEMVER" > build/rpm_metadata/release + echo "release: 0.${PREBUILD_NUMBER:-$GITHUB_RUN_NUMBER}.${PREBUILD_TAG}" >> build/rpm_metadata/release + printf '::warning ::Added file build/rpm_metadata/release with content "%s"\n' "$(cat build/rpm_metadata/release)" + else + echo "0.${PREBUILD_NUMBER:-$GITHUB_RUN_NUMBER}.${PREBUILD_TAG}" > build/rpm_metadata/release + printf '::warning ::Added file build/rpm_metadata/release with content "%s"\n' "$(cat build/rpm_metadata/release)" + fi + + - name: 'Customize RPM Release tag via build/rpm_metadata/release (RPM rebuild)' + if: ${{ github.event.inputs.rebuild_number != '' }} + env: + BUILD_SEMVER: ${{ steps.validate-inputs.outputs.build_semver }} + REBUILD_NUMBER: ${{ github.event.inputs.rebuild_number }} + run: | + mkdir -p build/rpm_metadata + # simp-doc uses a unique data format in /release + if [[ "$TARGET_REPO" =~ ^simp\/simp-doc$ ]]; then + echo "version: $BUILD_SEMVER" > build/rpm_metadata/release + echo "release: $REBUILD_NUMBER" > build/rpm_metadata/release + else + echo "$REBUILD_NUMBER" > build/rpm_metadata/release + fi + printf '::warning ::Added file build/rpm_metadata/release with content "%s"\n' "$(cat build/rpm_metadata/release)" + + - name: > + Build & Sign RPMs for + ${{ github.event.inputs.release_tag }} + Release (${{ github.event.inputs.build_container_os }}) + uses: simp/github-action-build-and-sign-pkg-single-rpm@v2 + id: build-and-sign-rpm + with: + gpg_signing_key: ${{ secrets.SIMP_DEV_GPG_SIGNING_KEY }} + gpg_signing_key_id: ${{ secrets.SIMP_DEV_GPG_SIGNING_KEY_ID }} + gpg_signing_key_passphrase: ${{ secrets.SIMP_DEV_GPG_SIGNING_KEY_PASSPHRASE }} + simp_core_ref_for_building_rpms: ${{ secrets.SIMP_CORE_REF_FOR_BUILDING_RPMS }} + simp_builder_docker_image: 'docker.io/simpproject/simp_build_${{ github.event.inputs.build_container_os }}:latest' + path_to_build: "${{ (github.event.inputs.path_to_build != null && format('{0}/{1}', github.workspace, github.event.inputs.path_to_build)) || github.workspace }}" + verbose: 'no' # ${{ github.event.inputs.verbose }} + + - name: "Wipe all previous assets from GitHub Release (when clean == 'yes')" + if: ${{ github.event.inputs.clean == 'yes' && github.event.inputs.dry_run != 'yes' }} + uses: actions/github-script@v6 + env: + release_id: ${{ steps.release-api.outputs.id }} + with: + github-token: ${{ github.event.inputs.target_repo_token || secrets.GITHUB_TOKEN }} + script: | + const release_id = process.env.release_id + const [owner, repo] = process.env.TARGET_REPO.split('/') + const existingAssets = await github.rest.repos.listReleaseAssets({ owner, repo, release_id }) + + console.log( ` !! !! Wiping ALL uploaded assets for ${owner}/${repo} release (id: ${release_id})`) + existingAssets.data.forEach(async function(asset){ + asset_id = asset.id + console.log( ` !! !! !! Wiping existing asset for ${asset.name} (id: ${asset_id})`) + await github.rest.repos.deleteReleaseAsset({ owner, repo, asset_id }) + }) + + - name: "Upload RPM file(s) to GitHub Release (dry_run != 'yes')" + if: ${{ github.event.inputs.dry_run != 'yes' }} + uses: actions/github-script@v6 + env: + rpm_file_paths: ${{ steps.build-and-sign-rpm.outputs.rpm_file_paths }} + rpm_gpg_file: ${{ steps.build-and-sign-rpm.outputs.rpm_gpg_file }} + release_id: ${{ steps.release-api.outputs.id }} + clobber: ${{ github.event.inputs.clobber }} + clean: ${{ github.event.inputs.clean }} + dry_run: ${{ github.event.inputs.dry_run }} + with: + github-token: ${{ github.event.inputs.target_repo_token || secrets.GITHUB_TOKEN }} + script: | + const path = require('path') + const fs = require('fs') + + async function clobberAsset (name, owner, repo, release_id ){ + console.log( ` -- clobber asset ${name}: owner: ${owner} repo: ${repo} release_id: ${release_id}` ) + + const existingAssets = await github.rest.repos.listReleaseAssets({ owner, repo, release_id }) + const matchingAssets = existingAssets.data.filter(item => item.name == name); + if ( matchingAssets.length > 0 ){ + asset_id = matchingAssets[0].id + console.log( ` !! !! Clobbering existing asset for ${name} (id: ${asset_id})`) + await github.rest.repos.deleteReleaseAsset({ owner, repo, asset_id }) + return(true) + } + return(false) + } + + async function uploadAsset(owner, repo, release_id, file, assetContentType ){ + console.log( `\n\n -- uploadAsset: owner: ${owner} repo: ${repo} release_id: ${release_id}, file: ${file}\n` ) + const name = path.basename(file) + + const data = fs.readFileSync(file) + const contentLength = fs.statSync(file).size + const headers = { + 'content-type': assetContentType, + 'content-length': contentLength + }; + + console.log( ` == Uploading asset ${name}: ${assetContentType}` ) + const uploadAssetResponse = await github.rest.repos.uploadReleaseAsset({ + owner, repo, release_id, data, name, headers, + }) + return( uploadAssetResponse ); + } + + console.log('== start'); + const release_id = process.env.release_id + const [owner, repo] = process.env.TARGET_REPO.split('/') + const clobber = process.env.clobber == 'yes'; + const rpm_files = process.env.rpm_file_paths.split(/[\r\n]+/); + const rpm_gpg_file = process.env.rpm_gpg_file; + + let uploaded_files = rpm_files.concat(rpm_gpg_file).map(function(file){ + const name = path.basename(file) + var content_type = 'application/pgp-keys' + if( name.match(/\.rpm$/) ){ + content_type = 'application/octet-stream' + } + + let conditionalClobber = new Promise((resolve,reject) => { + if ( clobber ){ + resolve(clobberAsset( name, owner, repo, release_id )) + return + } + resolve( false ) + }) + + conditionalClobber.then((clobbered)=> { + uploadAsset(owner, repo, release_id, file, content_type ) + }).then(result => result ) + }) + console.log('== done') diff --git a/.github/workflows/tag_deploy.yml b/.github/workflows/tag_deploy.yml new file mode 100644 index 0000000..10a5d1c --- /dev/null +++ b/.github/workflows/tag_deploy.yml @@ -0,0 +1,196 @@ +# Build & Deploy Puppet module & GitHub release when a SemVer tag is pushed +# ------------------------------------------------------------------------------ +# +# NOTICE: **This file is maintained with puppetsync** +# +# This file is updated automatically as part of a standardized asset baseline. +# +# The next baseline sync will overwrite any local changes to this file! +# +# ============================================================================== +# +# This pipeline uses the following GitHub Action Secrets: +# +# GitHub Secret variable Notes +# ------------------------------- --------------------------------------- +# PUPPETFORGE_API_TOKEN +# SIMP_CORE_REF_FOR_BUILDING_RPMS simp-core ref (tag) to use to build +# RPMs with `rake pkg:single` +# SIMP_DEV_GPG_SIGNING_KEY GPG signing key's secret key +# SIMP_DEV_GPG_SIGNING_KEY_ID User ID (name) of signing key +# SIMP_DEV_GPG_SIGNING_KEY_PASSPHRASE Passphrase to use GPG signing key +# +# ------------------------------------------------------------------------------ +# +# NOTES: +# +# * The CHANGELOG text is altered to remove RPM-style date headers, which don't +# render well as markdown on the GitHub release pages +# +--- +name: 'Tag: Release to GitHub w/RPMs + Puppet Forge' + +'on': + push: + tags: + # NOTE: These filter patterns aren't actually regexes: + # https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet + - '[0-9]+\.[0-9]+\.[0-9]+' + - '[0-9]+\.[0-9]+\.[0-9]+\-[a-z]+[0-9]+' + +env: + PUPPET_VERSION: '~> 8' + +jobs: + releng-checks: + name: "RELENG checks" + if: github.repository_owner == 'simp' + runs-on: ubuntu-latest + steps: + - name: "Assert '${{ github.ref }}' is a tag" + run: '[[ "$GITHUB_REF" =~ ^refs/tags/ ]] || { echo "::error ::GITHUB_REF is not a tag: ${GITHUB_REF}"; exit 1 ; }' + - uses: actions/checkout@v3 + with: + ref: ${{ github.ref }} + clean: true + - uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.2 + bundler-cache: true + - run: bundle exec rake pkg:check_version + - run: bundle exec rake pkg:compare_latest_tag + - run: bundle exec rake pkg:create_tag_changelog + - run: bundle exec rake metadata_lint + - name: "Test that Puppet module can build" + run: "bundle exec pdk build --force" + + + create-github-release: + name: Deploy GitHub Release + needs: + - releng-checks + if: github.repository_owner == 'simp' + runs-on: ubuntu-latest + outputs: + prerelease: ${{ steps.tag-check.outputs.prerelease }} + tag: ${{ steps.tag-check.outputs.tag }} + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + ref: ${{ github.ref }} + clean: true + fetch-depth: 0 + + - name: Get tag & annotation info (${{github.ref}}) + id: tag-check + run: | + tag="${GITHUB_REF/refs\/tags\//}" + annotation="$(git for-each-ref "$GITHUB_REF" --format='%(contents)' --count=1)" + annotation_title="$(echo "$annotation" | head -1)" + + if [[ "$tag" =~ ^(simp-|v)?[0-9]+\.[0-9]+\.[0-9]+(-(rc|alpha|beta|pre|post)?([0-9]+)?)?$ ]]; then + if [ -n "${BASH_REMATCH[2]}" ]; then + prerelease=yes + annotation_title="Pre-release of ${tag}" + fi + else + printf '::error ::Release Tag format is not SemVer, X.Y.Z-R, X.Y.Z-: "%s"\n' "$RELEASE_TAG" + exit 88 + fi + + echo "tag=$tag" | tee -a "$GITHUB_OUTPUT" + echo "prerelease=$prerelease" | tee -a "$GITHUB_OUTPUT" + echo "TARGET_TAG=$tag" | tee -a "$GITHUB_ENV" + + # Prepare annotation body as a file for the next step + # + # * The GitHub Release renders the text in this file as markdown + # * The `perl -pe` removes RPM-style date headers from the CHANGELOG, + # because they don't render well as markdown on the Release page + echo "RELEASE_MESSAGE<> "$GITHUB_ENV" + printf '%s\n\n' "$annotation_title" >> "$GITHUB_ENV" + echo "$annotation" | tail -n +2 | \ + perl -pe 'BEGIN{undef $/;} s/\n\* (Mon|Tue|Wed|Thu|Fri|Sat|Sun) .*?\n//smg;' >> "$GITHUB_ENV" + echo "EOF$$" >> "$GITHUB_ENV" + + - name: Create Release + id: create_release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + IS_PRERELEASE: ${{ steps.tag-check.outputs.prerelease }} + run: | + echo "${RELEASE_MESSAGE}" > /tmp/.commit-msg.txt + args=(-F /tmp/.commit-msg.txt) + [[ "$IS_PRERELEASE" == yes ]] && args+=(--prerelease) + + gh release create ${args[@]} "$TARGET_TAG" + + build-and-attach-rpms: + name: Trigger RPM release + needs: + - create-github-release + if: github.repository_owner == 'simp' + runs-on: ubuntu-latest + env: + TARGET_REPO: ${{ github.repository }} + strategy: + matrix: + os: + - centos7 + - centos8 + steps: + - name: Trigger RPM release workflow (${{ matrix.os }}) + uses: actions/github-script@v6 + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + TARGET_TAG: ${{ needs.create-github-release.outputs.tag }} + with: + github-token: ${{ secrets.SIMP_AUTO_GITHUB_TOKEN__REPO_SCOPE }} + script: | + console.log( `== Building tag: '${ process.env.TARGET_TAG }' for os '${{ matrix.os}}'` ) + const [owner, repo] = process.env.TARGET_REPO.split('/') + await github.request('POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches', { + owner: owner, + repo: repo, + workflow_id: 'release_rpms.yml', + ref: process.env.DEFAULT_BRANCH, + inputs: { + release_tag: process.env.TARGET_TAG, + clean: 'no', + clobber: 'yes', + build_container_os: '${{ matrix.os }}' + } + }).then((result) => { + console.log( `== Submitted workflow dispatch to build RPMs from ${{ matrix.os }}: status ${result.status}` ) + }) + + deploy-to-puppet-forge: + name: 'Deploy PuppetForge Release' + needs: + - create-github-release + if: (github.repository_owner == 'simp') && (needs.create-github-release.outputs.prerelease != 'yes') + runs-on: ubuntu-latest + env: + PUPPETFORGE_API_TOKEN: ${{ secrets.PUPPETFORGE_API_TOKEN }} + FORGE_USER_AGENT: GitHubActions-ForgeReleng-Workflow/0.4.1 (Purpose/forge-ops-for-${{ github.event.repository.name }}) + FORGE_API_URL: https://forgeapi.puppet.com/v3/releases + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + ref: ${{ github.ref }} + clean: true + - uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.2 + bundler-cache: true + - name: Build Puppet module (PDK) + run: bundle exec pdk build --force + - name: Deploy to Puppet Forge (skipped when prerelease) + run: | + curl -X POST --silent --show-error --fail \ + --user-agent "$FORGE_USER_AGENT" \ + --header "Authorization: Bearer ${PUPPETFORGE_API_TOKEN}" \ + --form "file=@$(find $PWD/pkg -name ''*.tar.gz'')" \ + "$FORGE_API_URL" diff --git a/.github/workflows/validate_tokens.yml b/.github/workflows/validate_tokens.yml new file mode 100644 index 0000000..cfbba7b --- /dev/null +++ b/.github/workflows/validate_tokens.yml @@ -0,0 +1,68 @@ +# Validate API tokens in GitHub Secrets against their respective services +# ------------------------------------------------------------------------------ +# +# NOTICE: **This file is maintained with puppetsync** +# +# This file is updated automatically as part of a puppet module baseline. +# +# The next baseline sync will overwrite any local changes to this file! +# +# ============================================================================== +# +# This pipeline uses the following GitHub Action Secrets: +# +# GitHub Secret variable Type Notes +# ------------------------ -------- ---------------------------------------- +# PUPPETFORGE_API_TOKEN Required +# NO_SCOPE_GITHUB_TOKEN Required GitHub token (should have no scopes) +# The secure vars will be filtered in GitHub Actions log output, and aren't +# provided to untrusted builds (i.e, triggered by PR from another repository) +# +--- +name: 'Manual: Validate API tokens' + +'on': + - workflow_dispatch + +jobs: + puppetforge: + name: 'Puppet Forge token authenticates with API' + runs-on: ubuntu-latest + env: + PUPPETFORGE_API_TOKEN: ${{ secrets.PUPPETFORGE_API_TOKEN }} + FORGE_USER_AGENT: GitHubActions-ForgeReleng-Workflow/0.4.0 (Purpose/forge-ops-for-${{ github.event.repository.name }}) + steps: + - run: | + curl -sS --fail --silent --show-error \ + --user-agent "$FORGE_USER_AGENT" \ + --header "Authorization: Bearer ${PUPPETFORGE_API_TOKEN:-default_content_to_cause_401_response}" \ + https://forgeapi.puppet.com/v3/users > /dev/null + + github-no-scope: + name: 'No-scope GitHub token has NO scopes' + runs-on: ubuntu-latest + env: + GITHUB_ORG: ${{ github.event.organization.login }} + NO_SCOPE_GITHUB_TOKEN: ${{secrets.NO_SCOPE_GITHUB_TOKEN}} + steps: + - name: Test token scopes with curl (expect no scopes) + run: | + if ! response="$(curl -I --http1.0 --fail --silent --show-error \ + --header 'Content-Type: application/json' \ + --header "Authorization: token ${NO_SCOPE_GITHUB_TOKEN:-default_content_to_cause_error}" \ + "https://api.github.com/users/${GITHUB_ORG}")" 2>/tmp/x.$$.err; then + echo "::error ::$(cat /tmp/x.$$.err)" + exit 1 + fi + + if ! scopes="$(echo "$response" | grep '^X-OAuth-Scopes:' )"; then + echo "::error ::No X-OAuth-Scopes in response headers!" + echo "::debug ::$response" + exit 1 + fi + scopes="$( echo "$scopes" | strings )" + if echo "$scopes" | awk -F: '{print $2}' | grep -E '\w' ; then + echo "::error ::The NO_SCOPE_GITHUB_TOKEN token has scopes! (${scopes})" + echo "::debug ::${scopes}" + exit 1 + fi diff --git a/.gitignore b/.gitignore index 238ce89..410b067 100644 --- a/.gitignore +++ b/.gitignore @@ -1,44 +1,27 @@ -.git/ -.*.sw[op] -*.log -.metadata +# ------------------------------------------------------------------------------ +# NOTICE: **This file is maintained with puppetsync** +# +# This file is automatically updated as part of a puppet module baseline. +# The next baseline sync will overwrite any local changes made to this file. +# ------------------------------------------------------------------------------ +.*.sw? .yardoc -.yardwarns -*.iml -/.bundle/ -/.idea/ -/.vagrant/ -/coverage/ -/bin/ -/doc/ -/data/LOCAL.yaml -/data/local.yaml -/Gemfile.local +.idea/ +dist +/pkg +# Read everything in fixtures +/spec/fixtures/* +# Un-ignore hieradata +!/spec/fixtures/hieradata/* +# Except this one, which is auto-generated +/spec/fixtures/hieradata/hiera.yaml +/spec/rp_env +/.rspec_system +/.vagrant +/.bundle +/.vendor +/vendor +/junit +/log +/doc /Gemfile.lock -/junit/ -/log/ -/pkg/ -/spec/fixtures/manifests/ -/spec/fixtures/modules/ -/tmp/ -/vendor/ -/convert_report.txt -/update_report.txt -.DS_Store -.project -.envrc -/inventory.yaml -/.modules/ -/.resource_types/ -/_slim_repos*.sh -/_slim_repos*.yaml -/_slim_repos*.repo -/_download_path/ -/run/ -/scripts/ -/settings/ -/pulp_storage/ -/.plan_cache.json -/.task_cache.json -bolt_pulp3_config.yaml -Puppetfile diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..e574af1 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,375 @@ +# ------------------------------------------------------------------------------ +# NOTICE: **This file is maintained with puppetsync** +# +# This file is updated automatically as part of a puppet module baseline. +# +# The next baseline sync will overwrite any local changes to everything above +# the line "# Repo-specific content" +# ------------------------------------------------------------------------------ +# The testing matrix considers ruby/puppet versions supported by SIMP and PE: +# +# https://puppet.com/docs/pe/latest/component_versions_in_recent_pe_releases.html +# https://puppet.com/misc/puppet-enterprise-lifecycle +# ------------------------------------------------------------------------------ +# Release Puppet Ruby EOL +# PE 2021.7 7.30 2.7.8 2025-02 (LTS) +# PE 2023.8 8.6 3.2.3 TBD +--- + +stages: + - 'validation' + - 'acceptance' + - 'compliance' + - 'deployment' + +variables: + # PUPPET_VERSION is a canary variable! + # + # The value `UNDEFINED` will (intentionally) cause `bundler install|update` to + # fail. The intended value for PUPPET_VERSION is provided by the `pup_#` YAML + # anchors. If it is still `UNDEFINED`, all the other setting from the job's + # anchor are also missing. + PUPPET_VERSION: 'UNDEFINED' # <- Matrixed jobs MUST override this (or fail) + BUNDLER_VERSION: '2.4.22' + SIMP_MATRIX_LEVEL: '1' + SIMP_FORCE_RUN_MATRIX: 'no' + + # Force dependencies into a path the gitlab-runner user can write to. + # (This avoids some failures on Runners with misconfigured ruby environments.) + GEM_HOME: .vendor/gem_install + BUNDLE_CACHE_PATH: .vendor/bundle + BUNDLE_PATH: .vendor/bundle + BUNDLE_BIN: .vendor/gem_install/bin + BUNDLE_NO_PRUNE: 'true' + +.snippets: + before_beaker_google: + # Logic for beaker-google environments + - echo -e "\e[0Ksection_start:`date +%s`:before_script05[collapsed=true]\r\e[0KGCP environment checks" + - "if [ \"$BEAKER_HYPERVISOR\" == google ]; then mkdir -p ~/.ssh; chmod 700 ~/.ssh; test -f ~/.ssh/google_compute_engine || ssh-keygen -f ~/.ssh/google_compute_engine < /dev/null; echo 'gem \"beaker-google\"' >> Gemfile.local ; fi" # yamllint disable rule:line-length + - echo -e "\e[0Ksection_end:`date +%s`:before_script05\r\e[0K" + + before: + # Print important environment variables that may affect this job + - 'ruby -e "puts %(\n\n), %q(=)*80, %(\nSIMP-relevant Environment Variables:\n\n#{e=ENV.keys.grep(/^PUPPET|^SIMP|^BEAKER|MATRIX|GOOGLE/); pad=((e.map{|x| x.size}.max||0)+1); e.map{|v| %( * #{%(#{v}:).ljust(pad)} #{39.chr + ENV[v] + 39.chr}\n)}.join}\n), %q(=)*80, %(\n\n)" || :' # yamllint disable rule:line-length + + - echo -e "\e[0Ksection_start:`date +%s`:before_script10[collapsed=true]\r\e[0KDiagnostic ruby & gem information" + # Diagnostic ruby & gem information + - 'which ruby && ruby --version || :' + - "[[ $- == *i* ]] && echo 'Interactive shell session' || echo 'Non-interactive shell session'" + - "shopt -q login_shell && echo 'Login shell' || echo 'Not a login shell'" + - 'rvm ls || :' + - echo -e "\e[0Ksection_end:`date +%s`:before_script10\r\e[0K" + + # If RVM is available, make SURE it's using the right Ruby: + # * Source rvm (to run in non-login shells) + # * Use $MATRIX_RUBY_VERSION ruby, install if not present + - echo -e "\e[0Ksection_start:`date +%s`:before_script20[collapsed=true]\r\e[0KEnsure RVM & ruby is installed" + - "if command -v rvm; then if declare -p rvm_path &> /dev/null; then source \"${rvm_path}/scripts/rvm\"; else source \"$HOME/.rvm/scripts/rvm\" || source /etc/profile.d/rvm.sh; fi; fi" + - >- + if command -v rvm && ! grep rvm_install_on_use_flag=1 ~/.rvmrc; then + echo rvm_install_on_use_flag=1 >> ~/.rvmrc + || echo '== WARNING: ~/.rvmrc is missing rvm_install_on_use_flag=1 and I failed to add it'; fi + - "if command -v rvm; then rvm use \"$MATRIX_RUBY_VERSION\"; else echo \"rvm not detected; skipping 'rvm use'\"; fi" + - 'ruby --version || :' + - 'gem list sync || :' + - echo -e "\e[0Ksection_end:`date +%s`:before_script20\r\e[0K" + + # Bundle gems (preferring cached > local > downloaded resources) + # * Try to use cached and local resources before downloading dependencies + - echo -e "\e[0Ksection_start:`date +%s`:before_script30[collapsed=true]\r\e[0KBundle gems (preferring cached > local > downloaded resources)" + - 'declare GEM_BUNDLER_VER=(-v "~> ${BUNDLER_VERSION:-2.4.22}")' + - 'declare GEM_INSTALL_CMD=(gem install --no-document)' + - 'declare BUNDLER_INSTALL_CMD=(bundle install --no-binstubs --jobs $(nproc) "${FLAGS[@]}")' + - 'mkdir -p ${GEM_HOME} ${BUNDLER_BIN}' + - 'gem list -ie "${GEM_BUNDLER_VER[@]}" --silent bundler || "${GEM_INSTALL_CMD[@]}" --local "${GEM_BUNDLER_VER[@]}" bundler || "${GEM_INSTALL_CMD[@]}" "${GEM_BUNDLER_VER[@]}" bundler' + - 'rm -rf pkg/ || :' + - >- + bundle check + || rm -f Gemfile.lock + && ("${BUNDLER_INSTALL_CMD[@]}" --local + || "${BUNDLER_INSTALL_CMD[@]}" + || bundle pristine + || "${BUNDLER_INSTALL_CMD[@]}") + || { echo "PIPELINE: Bundler could not install everything (see log output above)" && exit 99 ; } + - echo -e "\e[0Ksection_end:`date +%s`:before_script30\r\e[0K" + + # Diagnostic bundler, ruby, and gem checks: + - echo -e "\e[0Ksection_start:`date +%s`:before_script40[collapsed=true]\r\e[0KDiagnostic bundler, ruby, and gem checks" + - 'bundle exec rvm ls || :' + - 'bundle exec which ruby || :' + - 'bundle show sync || :' + - 'bundle exec gem list sync || :' + - echo -e "\e[0Ksection_end:`date +%s`:before_script40\r\e[0K" + +# bundler dependencies and caching +# +# - Cache bundler gems between pipelines foreach Ruby version +# - Try to use cached and local resources before downloading dependencies +# -------------------------------------- +.setup_bundler_env: &setup_bundler_env + cache: + key: "${CI_PROJECT_NAMESPACE}_ruby-${MATRIX_RUBY_VERSION}_bundler" + paths: + - '.vendor' + before_script: + !reference [.snippets, before] + + +# Assign a matrix level when your test will run. Heavier jobs get higher numbers +# NOTE: To skip all jobs with a SIMP_MATRIX_LEVEL, set SIMP_MATRIX_LEVEL=0 + +.relevant_file_conditions_trigger_spec_tests: &relevant_file_conditions_trigger_spec_tests + changes: + - .gitlab-ci.yml + - .fixtures.yml + - "spec/spec_helper.rb" + - "spec/{classes,unit,defines,type_aliases,types,hosts,lib}/**/*.rb" + - "{SIMP,data,manifests,files,types,lib,functions}/**/*" + - "templates/**/*.{erb,epp}" + - "Gemfile" + exists: + - "spec/{classes,unit,defines,type_aliases,types,hosts}/**/*_spec.rb" + +.relevant_file_conditions_trigger_acceptance_tests: &relevant_file_conditions_trigger_acceptance_tests + changes: + - .gitlab-ci.yml + - .fixtures.yml + - "spec/spec_helper_acceptance.rb" + - "spec/{helpers,acceptance}/**/*" + - "spec/inspec_*/**/*" + - "{SIMP,data,manifests,files,types,lib,functions}/**/*" + - "templates/**/*.{erb,epp}" + - "Gemfile" + exists: + - "spec/acceptance/**/*_spec.rb" + +# For some reason, the rule regexes stopped matching line starts inside +# $CI_COMMIT_MESSAGE with carets /^/, so we're using /\n?/ as a workaround. +.skip_job_when_commit_message_says_to: &skip_job_when_commit_message_says_to + if: '$CI_COMMIT_MESSAGE =~ /\n?CI: (SKIP MATRIX|MATRIX LEVEL 0)/' + when: never + +.force_run_job_when_commit_message_lvl_1_or_above: &force_run_job_when_commit_mssage_lvl_1_or_above + if: '$CI_COMMIT_MESSAGE =~ /\n?CI: MATRIX LEVEL [123]/' + when: on_success + +.force_run_job_when_commit_message_lvl_2_or_above: &force_run_job_when_commit_mssage_lvl_2_or_above + if: '$CI_COMMIT_MESSAGE =~ /\n?CI: MATRIX LEVEL [23]/' + when: on_success + +.force_run_job_when_commit_message_lvl_3_or_above: &force_run_job_when_commit_mssage_lvl_3_or_above + if: '$CI_COMMIT_MESSAGE =~ /\n?CI: MATRIX LEVEL [3]/' + when: on_success + +# check for $CI_PIPELINE_SOURCE needed because this is combined w/when:changes +.run_job_when_level_1_or_above_w_changes: &run_job_when_level_1_or_above_w_changes + if: '$SIMP_MATRIX_LEVEL =~ /^[123]$/ && $CI_COMMIT_BRANCH && $CI_PIPELINE_SOURCE == "push"' + when: on_success + +.run_job_when_level_2_or_above_w_changes: &run_job_when_level_2_or_above_w_changes + if: '$SIMP_MATRIX_LEVEL =~ /^[23]$/ && $CI_COMMIT_BRANCH && $CI_PIPELINE_SOURCE == "push"' + when: on_success + +.run_job_when_level_3_or_above_w_changes: &run_job_when_level_3_or_above_w_changes + if: '$SIMP_MATRIX_LEVEL =~ /^[3]$/ && $CI_COMMIT_BRANCH && $CI_PIPELINE_SOURCE == "push"' + when: on_success + +.force_run_job_when_var_and_lvl_1_or_above: &force_run_job_when_var_and_lvl_1_or_above + if: '$SIMP_FORCE_RUN_MATRIX == "yes" && $SIMP_MATRIX_LEVEL =~ /^[123]$/' + when: on_success + +.force_run_job_when_var_and_lvl_2_or_above: &force_run_job_when_var_and_lvl_2_or_above + if: '$SIMP_FORCE_RUN_MATRIX == "yes" && $SIMP_MATRIX_LEVEL =~ /^[23]$/' + when: on_success + +.force_run_job_when_var_and_lvl_3_or_above: &force_run_job_when_var_and_lvl_3_or_above + if: '$SIMP_FORCE_RUN_MATRIX == "yes" && $SIMP_MATRIX_LEVEL =~ /^[3]$/' + when: on_success + + +# SIMP_MATRIX_LEVEL=1: Intended to run every commit +.with_SIMP_ACCEPTANCE_MATRIX_LEVEL_1: &with_SIMP_ACCEPTANCE_MATRIX_LEVEL_1 + rules: + - <<: *skip_job_when_commit_message_says_to + - <<: *force_run_job_when_var_and_lvl_1_or_above + - <<: *force_run_job_when_commit_mssage_lvl_1_or_above + - <<: *run_job_when_level_1_or_above_w_changes + <<: *relevant_file_conditions_trigger_acceptance_tests + - when: never + +.with_SIMP_SPEC_MATRIX_LEVEL_1: &with_SIMP_SPEC_MATRIX_LEVEL_1 + rules: + - <<: *skip_job_when_commit_message_says_to + - <<: *force_run_job_when_commit_mssage_lvl_1_or_above + - <<: *force_run_job_when_var_and_lvl_1_or_above + - <<: *run_job_when_level_1_or_above_w_changes + <<: *relevant_file_conditions_trigger_spec_tests + - when: never + +# SIMP_MATRIX_LEVEL=2: Resource-heavy or redundant jobs +.with_SIMP_ACCEPTANCE_MATRIX_LEVEL_2: &with_SIMP_ACCEPTANCE_MATRIX_LEVEL_2 + rules: + - <<: *skip_job_when_commit_message_says_to + - <<: *force_run_job_when_var_and_lvl_2_or_above + - <<: *force_run_job_when_commit_mssage_lvl_2_or_above + - <<: *run_job_when_level_2_or_above_w_changes + <<: *relevant_file_conditions_trigger_acceptance_tests + - when: never + +.with_SIMP_SPEC_MATRIX_LEVEL_2: &with_SIMP_SPEC_MATRIX_LEVEL_2 + rules: + - <<: *skip_job_when_commit_message_says_to + - <<: *force_run_job_when_commit_mssage_lvl_2_or_above + - <<: *force_run_job_when_var_and_lvl_2_or_above + - <<: *run_job_when_level_2_or_above_w_changes + <<: *relevant_file_conditions_trigger_spec_tests + - when: never + +# SIMP_MATRIX_LEVEL=3: Reserved for FULL matrix testing +.with_SIMP_ACCEPTANCE_MATRIX_LEVEL_3: &with_SIMP_ACCEPTANCE_MATRIX_LEVEL_3 + rules: + - <<: *skip_job_when_commit_message_says_to + - <<: *force_run_job_when_var_and_lvl_3_or_above + - <<: *force_run_job_when_commit_mssage_lvl_3_or_above + - <<: *run_job_when_level_3_or_above_w_changes + <<: *relevant_file_conditions_trigger_acceptance_tests + - when: never + + +# Puppet Versions +# ----------------------------------------------------------------------- + +.pup_7_x: &pup_7_x + image: 'ruby:2.7' + variables: + PUPPET_VERSION: '~> 7.0' + BEAKER_PUPPET_COLLECTION: 'puppet7' + MATRIX_RUBY_VERSION: '2.7' + +.pup_7_pe: &pup_7_pe + image: 'ruby:2.7' + variables: + PUPPET_VERSION: '7.21.0' + BEAKER_PUPPET_COLLECTION: 'puppet7' + MATRIX_RUBY_VERSION: '2.7' + +.pup_8_x: &pup_8_x + image: 'ruby:3.2' + variables: + PUPPET_VERSION: '~> 8.0' + BEAKER_PUPPET_COLLECTION: 'puppet8' + MATRIX_RUBY_VERSION: '3.2' + + +# Testing Environments +# ----------------------------------------------------------------------- + +.lint_tests: &lint_tests + stage: 'validation' + tags: ['docker'] + <<: *setup_bundler_env + script: + - 'bundle exec rake syntax' + - 'bundle exec rake lint' + - 'bundle exec rake metadata_lint' + +.unit_tests: &unit_tests + stage: 'validation' + tags: ['docker'] + <<: *setup_bundler_env + <<: *with_SIMP_SPEC_MATRIX_LEVEL_1 + script: + - 'bundle exec rake spec' + +.beaker: &beaker + image: ruby:2.7.2 # must be 2.7.2 if running in GCP + tags: + - beaker + before_script: + - !reference [.snippets, before_beaker_google] + - !reference [.snippets, before] + + +.acceptance_base: &acceptance_base + stage: 'acceptance' + <<: *setup_bundler_env + <<: *with_SIMP_ACCEPTANCE_MATRIX_LEVEL_1 + <<: *beaker + +.compliance_base: &compliance_base + stage: 'compliance' + <<: *setup_bundler_env + <<: *with_SIMP_ACCEPTANCE_MATRIX_LEVEL_1 + <<: *beaker + + +# Pipeline / testing matrix +# ======================================================================= + +releng_checks: + <<: *pup_7_x + <<: *setup_bundler_env + stage: 'validation' + tags: ['docker'] + script: + - 'command -v rpm || if command -v apt-get; then apt-get update; apt-get install -y rpm; fi ||:' + - 'bundle exec rake check:dot_underscore' + - 'bundle exec rake check:test_file' + - 'bundle exec rake pkg:check_version' + - 'bundle exec rake pkg:compare_latest_tag[,true]' + - 'bundle exec rake pkg:create_tag_changelog' + - 'bundle exec pdk build --force --target-dir=dist' + + +# Linting +# ----------------------------------------------------------------------- + +# NOTE: Don't add more lint checks here. +# puppet-lint is a validator, not a parser; it includes its own lexer and +# doesn't use the Puppet gem at all. Running multiple lint tests against +# different Puppet versions won't accomplish anything. + +pup-lint: + <<: *pup_7_x + <<: *lint_tests + +# Unit Tests +# ----------------------------------------------------------------------- + +pup7.x-unit: + <<: *pup_7_x + <<: *unit_tests + <<: *with_SIMP_SPEC_MATRIX_LEVEL_2 + +pup7.pe-unit: + <<: *pup_7_pe + <<: *unit_tests + +pup8.x-unit: + <<: *pup_8_x + <<: *unit_tests + +# ------------------------------------------------------------------------------ +# NOTICE: **This file is maintained with puppetsync** +# +# Everything above the "Repo-specific content" comment will be overwritten by +# the next puppetsync. +# ------------------------------------------------------------------------------ + +# Repo-specific content +# ============================================================================== +# +# Comments regarding repo-specific pipeline content: +# +# * All content BELOW the line above is repo-specific (including this comment). +# - It is safe to add and maintain repo-specific content within this section. +# - Subsequent syncs may modify the content (e.g., to update specific YAML +# anchor names), but they won't remove or replace it. +# +# * Reminder: All content ABOVE the line is managed by puppetsync. +# - Comment above the line will be overwritten by subsequent syncs +# - DO NOT remove the "# Repo-specific content" comment or the line after it. +# +# * Corollary: It is safe to remove/replace this comment with your own content. +# diff --git a/.pdkignore b/.pdkignore new file mode 100644 index 0000000..84caa42 --- /dev/null +++ b/.pdkignore @@ -0,0 +1,57 @@ +# .pdkignore masks files from inclusion by `pdk build`. +# +# It is used by CI when building modules to publish to the Puppet Forge and to +# mask symlinks from the `pdk build` test in the module's RELENG checks. +# ------------------------------------------------------------------------------ +# NOTICE: **This file is maintained with puppetsync** +# +# This file is automatically updated as part of a puppet module baseline. +# The next baseline sync will overwrite any local changes made to this file. +# ------------------------------------------------------------------------------ +.*.sw? +.git/ +.metadata +.yardoc +.yardwarns +*.iml +/.bundle/ +/.idea/ +/.vagrant/ +/coverage/ +/bin/ +/doc/ +/Gemfile.local +/Gemfile.lock +/junit/ +/log/ +/pkg/ +/dist/ +/tmp/ +/vendor/ +/.vendor/ +/convert_report.txt +/update_report.txt +.DS_Store +.project +.envrc +/inventory.yaml +/appveyor.yml +/.fixtures.yml +/Gemfile +/.gitattributes +/.gitignore +/.github/ +/.gitlab-ci.yml +/.pdkignore +/.puppet-lint.rc +/.sync.yml +/.pmtignore +/Rakefile +/rakelib/ +/.rspec +/.rubocop.yml +/.travis.yml +/.yardopts +/spec/ +/.vscode/ +/tests/ diff --git a/.puppet-lint.rc b/.puppet-lint.rc new file mode 100644 index 0000000..eb56769 --- /dev/null +++ b/.puppet-lint.rc @@ -0,0 +1,15 @@ +# ------------------------------------------------------------------------------ +# NOTICE: **This file is maintained with puppetsync** +# +# This file is automatically updated as part of a puppet module baseline. +# The next baseline sync will overwrite any local changes made to this file. +# ------------------------------------------------------------------------------ +--log-format="%{path}:%{line}:%{check}:%{KIND}:%{message}" +--relative +--no-class_inherits_from_params_class-check +--no-140chars-check +--no-trailing_comma-check +--no-params-empty-string-assignment-check +# This is here because the code can't handle lookups in parameters and SIMP +# modules have a LOT of those +--no-parameter_order-check diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..4cd92bf --- /dev/null +++ b/.rspec @@ -0,0 +1,3 @@ +--format documentation +--color +--fail-fast diff --git a/Gemfile b/Gemfile index 8b08052..e74c3da 100644 --- a/Gemfile +++ b/Gemfile @@ -1,17 +1,55 @@ -# frozen_string_literal: true +# ------------------------------------------------------------------------------ +# NOTICE: **This file is maintained with puppetsync** +# +# This file is automatically updated as part of a puppet module baseline. +# The next baseline sync will overwrite any local changes made to this file. +# ------------------------------------------------------------------------------ +gem_sources = ENV.fetch('GEM_SERVERS', 'https://rubygems.org').split(%r{[, ]+}) -source "https://rubygems.org" +ENV['PDK_DISABLE_ANALYTICS'] ||= 'true' -git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } +gem_sources.each { |gem_source| source gem_source } -# gem "rails" -gem 'pulpcore_client', '~> 3.22.2' -gem 'pulp_rpm_client', '~> 3.19.0' -gem 'bindata' -gem 'down' -gem 'logging' -gem 'pry' +group :test do + puppet_version = ENV.fetch('PUPPET_VERSION', ['>= 7', '< 9']) + major_puppet_version = Array(puppet_version).first.scan(%r{(\d+)(?:\.|\Z)}).flatten.first.to_i + gem 'hiera-puppet-helper' + gem 'metadata-json-lint' + gem 'pathspec', '~> 0.2' if Gem::Requirement.create('< 2.6').satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) + gem('pdk', ENV.fetch('PDK_VERSION', ['>= 2.0', '< 4.0']), require: false) if major_puppet_version > 5 + gem 'puppet', puppet_version + gem 'puppetlabs_spec_helper' + gem 'puppet-lint-trailing_comma-check', require: false + gem 'puppet-strings' + gem 'rake' + gem 'rspec' + gem 'rspec-puppet' + gem 'simp-rake-helpers', ENV.fetch('SIMP_RAKE_HELPERS_VERSION', ['>= 5.21.0', '< 6']) + gem 'simp-rspec-puppet-facts', ENV.fetch('SIMP_RSPEC_PUPPET_FACTS_VERSION', '~> 3.7') +end group :development do - gem 'puppet-lint' + gem 'pry' + gem 'pry-byebug' + gem 'pry-doc' +end + +group :system_tests do + gem 'bcrypt_pbkdf' + gem 'beaker' + gem 'beaker-rspec' + gem 'simp-beaker-helpers', ENV.fetch('SIMP_BEAKER_HELPERS_VERSION', ['>= 1.32.1', '< 2']) +end + +# Evaluate extra gemfiles if they exist +extra_gemfiles = [ + ENV.fetch('EXTRA_GEMFILE', ''), + "#{__FILE__}.project", + "#{__FILE__}.local", + File.join(Dir.home, '.gemfile'), +] +extra_gemfiles.each do |gemfile| + if File.file?(gemfile) && File.readable?(gemfile) + eval(File.read(gemfile), binding) # rubocop:disable Security/Eval + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..9b493bf --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,169 @@ +# frozen_string_literal: true + +# +# ------------------------------------------------------------------------------ +# NOTICE: **This file is maintained with puppetsync** +# +# This file is automatically updated as part of a puppet module baseline. +# The next baseline sync will overwrite any local changes made to this file. +# ------------------------------------------------------------------------------ + +require 'puppetlabs_spec_helper/module_spec_helper' +require 'rspec-puppet' +require 'simp/rspec-puppet-facts' +include Simp::RspecPuppetFacts + +require 'pathname' + +# RSpec Material +fixture_path = File.expand_path(File.join(__FILE__, '..', 'fixtures')) +module_name = File.basename(File.expand_path(File.join(__FILE__, '../..'))) + +if ENV['PUPPET_DEBUG'] + Puppet::Util::Log.level = :debug + Puppet::Util::Log.newdestination(:console) +end + +default_hiera_config = <<~HIERA_CONFIG +--- +version: 5 +hierarchy: + - name: Custom Test Hiera + path: "%{custom_hiera}.yaml" + - name: "%{module_name}" + path: "%{module_name}.yaml" + - name: Common + path: default.yaml +defaults: + data_hash: yaml_data + datadir: "stub" +HIERA_CONFIG + +# This can be used from inside your spec tests to set the testable environment. +# You can use this to stub out an ENC. +# +# Example: +# +# context 'in the :foo environment' do +# let(:environment){:foo} +# ... +# end +# +def set_environment(environment = :production) + RSpec.configure { |c| c.default_facts['environment'] = environment.to_s } +end + +# This can be used from inside your spec tests to load custom hieradata within +# any context. +# +# Example: +# +# describe 'some::class' do +# context 'with version 10' do +# let(:hieradata){ "#{class_name}_v10" } +# ... +# end +# end +# +# Then, create a YAML file at spec/fixtures/hieradata/some__class_v10.yaml. +# +# Hiera will use this file as it's base of information stacked on top of +# 'default.yaml' and .yaml per the defaults above. +# +# Note: Any colons (:) are replaced with underscores (_) in the class name. +def set_hieradata(hieradata) + RSpec.configure { |c| c.default_facts['custom_hiera'] = hieradata } +end + +unless File.directory?(File.join(fixture_path, 'hieradata')) + FileUtils.mkdir_p(File.join(fixture_path, 'hieradata')) +end + +unless File.directory?(File.join(fixture_path, 'modules', module_name)) + FileUtils.mkdir_p(File.join(fixture_path, 'modules', module_name)) +end + +RSpec.configure do |c| + # If nothing else... + c.default_facts = { + production: { + # :fqdn => 'production.rspec.test.localdomain', + path: '/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin', + concat_basedir: '/tmp' + } + } + + c.mock_framework = :rspec + c.mock_with :rspec + + c.module_path = File.join(fixture_path, 'modules') + + c.hiera_config = File.join(fixture_path, 'hieradata', 'hiera.yaml') + + # Useless backtrace noise + backtrace_exclusion_patterns = [ + %r{spec_helper}, + %r{gems}, + ] + + if c.respond_to?(:backtrace_exclusion_patterns) + c.backtrace_exclusion_patterns = backtrace_exclusion_patterns + elsif c.respond_to?(:backtrace_clean_patterns) + c.backtrace_clean_patterns = backtrace_exclusion_patterns + end + + # rubocop:disable RSpec/BeforeAfterAll + c.before(:all) do + data = YAML.safe_load(default_hiera_config) + data.each_key do |key| + next unless data[key].is_a?(Hash) + + if data[key][:datadir] == 'stub' + data[key][:datadir] = File.join(fixture_path, 'hieradata') + elsif data[key]['datadir'] == 'stub' + data[key]['datadir'] = File.join(fixture_path, 'hieradata') + end + end + + File.open(c.hiera_config, 'w') do |f| + f.write data.to_yaml + end + end + # rubocop:enable RSpec/BeforeAfterAll + + c.before(:each) do + @spec_global_env_temp = Dir.mktmpdir('simpspec') + + if defined?(environment) + set_environment(environment) + FileUtils.mkdir_p(File.join(@spec_global_env_temp, environment.to_s)) + end + + # ensure the user running these tests has an accessible environmentpath + Puppet[:digest_algorithm] = 'sha256' + Puppet[:environmentpath] = @spec_global_env_temp + Puppet[:user] = Etc.getpwuid(Process.uid).name + Puppet[:group] = Etc.getgrgid(Process.gid).name + + # sanitize hieradata + if defined?(hieradata) + set_hieradata(hieradata.tr(':', '_')) + elsif defined?(class_name) + set_hieradata(class_name.tr(':', '_')) + end + end + + c.after(:each) do + # clean up the mocked environmentpath + FileUtils.rm_rf(@spec_global_env_temp) + @spec_global_env_temp = nil + end +end + +Dir.glob("#{RSpec.configuration.module_path}/*").each do |dir| + begin + Pathname.new(dir).realpath + rescue StandardError + raise "ERROR: The module '#{dir}' is not installed. Tests cannot continue." + end +end