diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..0275e1036 --- /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 000000000..4f101eb61 --- /dev/null +++ b/.github/workflows/add_new_issue_to_triage_project.yml @@ -0,0 +1,21 @@ +--- +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 000000000..9c0e6831c --- /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 000000000..04f1ef574 --- /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 000000000..10a5d1c05 --- /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 000000000..cfbba7bce --- /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 668bf64af..410b06794 100644 --- a/.gitignore +++ b/.gitignore @@ -1,36 +1,27 @@ +# ------------------------------------------------------------------------------ +# 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? -.bundle/ -.dev_gpgkeys -.r10k_cache/ -.tmp/ -tmp/ -.vagrant/ -.vscode/ -Gemfile.lock -Puppetfile.lock -ISO -SIMP_ISO -SIMP_ISO_STAGING -build/**/DVD_Overlay -build/**/GPGKEYS/.dropped_keys -build/**/GPGKEYS/dist -build/**/SIMP -build/**/build_keys/* -build/distributions/*/*/*/DVD/RPM-GPG-KEY-SIMP-Dev -build/yum_data/*/packages -log/ -pkg/ -sec_results/ -spec/fixtures -src/assets/* -!src/assets/simp -src/assets/simp/dist -src/build/autorequires -src/dist -src/doc -src/puppet/bootstrap/dist -src/puppet/bootstrap/environments/simp/hieradata/.simp_migrated -src/puppet/modules -src/rsync -src/rubygems -src/utils/dist +.yardoc +.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 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5dd203394..c0a134846 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,12 +1,19 @@ # ------------------------------------------------------------------------------ +# 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/2019.8/component_versions_in_recent_pe_releases.html +# https://puppet.com/docs/pe/latest/component_versions_in_recent_pe_releases.html # https://puppet.com/misc/puppet-enterprise-lifecycle -# https://puppet.com/docs/pe/2018.1/overview/getting_support_for_pe.html # ------------------------------------------------------------------------------ # Release Puppet Ruby EOL -# PE 2019.8 6.22 2.5.7 2022-12 (LTS) +# PE 2021.7 7.30 2.7.8 2025-02 (LTS) +# PE 2023.8 8.6 3.2.3 TBD --- stages: @@ -22,33 +29,29 @@ variables: # 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.2.19' + 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 + GEM_HOME: .vendor/gem_install BUNDLE_CACHE_PATH: .vendor/bundle - BUNDLE_PATH: .vendor/bundle - BUNDLE_BIN: .vendor/gem_install/bin - BUNDLE_NO_PRUNE: 'true' - - -# 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: + 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/); 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)" || :' + - '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 @@ -63,7 +66,10 @@ variables: # * 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 && ! 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 || :' @@ -72,13 +78,20 @@ variables: # 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.2.6}")' + - '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 ; }' + - >- + 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: @@ -89,23 +102,143 @@ variables: - '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_6_x: &pup_6_x - image: 'ruby:2.5' - variables: - PUPPET_VERSION: '~> 6.0' - BEAKER_PUPPET_COLLECTION: 'puppet6' - MATRIX_RUBY_VERSION: '2.5' -.pup_6_pe: &pup_6_pe - image: 'ruby:2.5' - variables: - PUPPET_VERSION: '6.22.1' - BEAKER_PUPPET_COLLECTION: 'puppet6' - MATRIX_RUBY_VERSION: '2.5' +# Puppet Versions +# ----------------------------------------------------------------------- .pup_7_x: &pup_7_x image: 'ruby:2.7' @@ -114,85 +247,117 @@ variables: 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' - tags: ['beaker'] <<: *setup_bundler_env + <<: *with_SIMP_ACCEPTANCE_MATRIX_LEVEL_1 + <<: *beaker .compliance_base: &compliance_base stage: 'compliance' - tags: ['beaker'] <<: *setup_bundler_env + <<: *with_SIMP_ACCEPTANCE_MATRIX_LEVEL_1 + <<: *beaker # Pipeline / testing matrix -#======================================================================= +# ======================================================================= releng_checks: - <<: *pup_6_x + <<: *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 ||:' - - 'SIMP_RPM_dist=.el7 bundle exec rake check:dot_underscore' - - 'SIMP_RPM_dist=.el7 bundle exec rake check:test_file' - - 'bundle exec rake metadata_lint' + - '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' -# Project-specific test control variables -#======================================================================= +# 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. +# ------------------------------------------------------------------------------ -# Anchor to disable release flavored tests, and run them only when -# the environment variable SIMP_RELEASE_TESTS is set in the GitLab repo settings -.only_with_SIMP_RELEASE_TESTS: &only_with_SIMP_RELEASE_TESTS - only: - variables: - - $SIMP_RELEASE_TESTS - -# To avoid running a prohibitive number of tests every commit, -# don't set this env var in your gitlab instance -.only_with_SIMP_FULL_MATRIX: &only_with_SIMP_FULL_MATRIX - only: - variables: - - $SIMP_FULL_MATRIX - -# When you want to run the ipa suite tests along with the default suite tests -# or run the full matrix -.only_with_SIMP_FULL_MATRIX_or_SIMP_IPA_TEST: &only_with_SIMP_FULL_MATRIX_or_SIMP_IPA_TEST - only: - variables: - - $SIMP_FULL_MATRIX - - $SIMP_IPA_TEST - -# When you want to run the simp_lite suite tests along with the default suite tests -# or run the full matrix -.only_with_SIMP_FULL_MATRIX_or_SIMP_LITE_TEST: &only_with_SIMP_FULL_MATRIX_or_SIMP_LITE_TEST - only: - variables: - - $SIMP_FULL_MATRIX - - $SIMP_LITE_TEST - -# Acceptance tests +# Repo-specific content # ============================================================================== pup6.pe-default_el7: diff --git a/.pmtignore b/.pmtignore deleted file mode 100644 index 9a2458bc8..000000000 --- a/.pmtignore +++ /dev/null @@ -1,15 +0,0 @@ -.fixtures.yml -.git* -.vagrant* -.r10k* -.bundle* -Gemfile* -ISO/ -Puppetfile* -build/ -junit/ -log/ -sec_results/ -spec/ -src/ -vendor/ diff --git a/.puppet-lint.rc b/.puppet-lint.rc new file mode 100644 index 000000000..eb5676990 --- /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/Gemfile b/Gemfile index d812a4e56..e74c3dad6 100644 --- a/Gemfile +++ b/Gemfile @@ -1,30 +1,31 @@ -gem_sources = ENV.fetch('GEM_SERVERS','https://rubygems.org').split(/[, ]+/) +# ------------------------------------------------------------------------------ +# 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{[, ]+}) ENV['PDK_DISABLE_ANALYTICS'] ||= 'true' gem_sources.each { |gem_source| source gem_source } group :test do - puppet_version = ENV['PUPPET_VERSION'] || '~> 6.22' - major_puppet_version = puppet_version.scan(/(\d+)(?:\.|\Z)/).flatten.first.to_i - gem 'rake' - gem 'terminal-table' - gem 'naturally' - gem 'puppet', puppet_version - gem 'rspec' - gem 'rspec-puppet' + 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 'puppetlabs_spec_helper' gem 'metadata-json-lint' - gem 'puppet-strings' - gem 'puppet-lint-empty_string-check', :require => false - gem 'puppet-lint-trailing_comma-check', :require => false - gem 'simp-rspec-puppet-facts', ENV['SIMP_RSPEC_PUPPET_FACTS_VERSION'] || '~> 3.1' - gem 'simp-rake-helpers', ENV['SIMP_RAKE_HELPERS_VERSION'] || ['>= 5.17.1', '< 6'] - gem( 'pdk', ENV['PDK_VERSION'] || '~> 2.0', :require => false) if major_puppet_version > 5 gem 'pathspec', '~> 0.2' if Gem::Requirement.create('< 2.6').satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) - gem 'simp-build-helpers', ENV['SIMP_BUILD_HELPERS_VERSION'] || ['> 0.1', '< 2.0'] - + 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 @@ -34,20 +35,21 @@ group :development do end group :system_tests do + gem 'bcrypt_pbkdf' gem 'beaker' gem 'beaker-rspec' - gem 'simp-beaker-helpers', ENV['SIMP_BEAKER_HELPERS_VERSION'] || ['>= 1.23.2', '< 2'] + gem 'simp-beaker-helpers', ENV.fetch('SIMP_BEAKER_HELPERS_VERSION', ['>= 1.32.1', '< 2']) end # Evaluate extra gemfiles if they exist extra_gemfiles = [ - ENV['EXTRA_GEMFILE'] || '', + 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) + eval(File.read(gemfile), binding) # rubocop:disable Security/Eval end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f2014d05c..e076f2384 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,13 @@ +# 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' @@ -7,32 +17,27 @@ # RSpec Material fixture_path = File.expand_path(File.join(__FILE__, '..', 'fixtures')) -module_name = File.basename(File.expand_path(File.join(__FILE__,'../..'))) +module_name = File.basename(File.expand_path(File.join(__FILE__, '../..'))) -# Add fixture lib dirs to LOAD_PATH. Work-around for PUP-3336 -if Puppet.version < "4.0.0" - Dir["#{fixture_path}/modules/*/lib"].entries.each do |lib_dir| - $LOAD_PATH << lib_dir - end +if ENV['PUPPET_DEBUG'] + Puppet::Util::Log.level = :debug + Puppet::Util::Log.newdestination(:console) end - -if !ENV.key?( 'TRUSTED_NODE_DATA' ) - warn '== WARNING: TRUSTED_NODE_DATA is unset, using TRUSTED_NODE_DATA=yes' - ENV['TRUSTED_NODE_DATA']='yes' -end - -default_hiera_config =<<-EOM +default_hiera_config = <<~HIERA_CONFIG --- -:backends: - - "yaml" -:yaml: - :datadir: "stub" -:hierarchy: - - "%{custom_hiera}" - - "%{module_name}" - - "default" -EOM +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. @@ -70,36 +75,36 @@ def set_hieradata(hieradata) RSpec.configure { |c| c.default_facts['custom_hiera'] = hieradata } end -if not File.directory?(File.join(fixture_path,'hieradata')) then - FileUtils.mkdir_p(File.join(fixture_path,'hieradata')) +unless File.directory?(File.join(fixture_path, 'hieradata')) + FileUtils.mkdir_p(File.join(fixture_path, 'hieradata')) end -if not File.directory?(File.join(fixture_path,'modules',module_name)) then - FileUtils.mkdir_p(File.join(fixture_path,'modules',module_name)) +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' + 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 :mocha + c.mock_with :rspec c.module_path = File.join(fixture_path, 'modules') - c.manifest_dir = File.join(fixture_path, 'manifests') + c.manifest_dir = File.join(fixture_path, 'manifests') if c.respond_to?(:manifest_dir) - c.hiera_config = File.join(fixture_path,'hieradata','hiera.yaml') + c.hiera_config = File.join(fixture_path, 'hieradata', 'hiera.yaml') # Useless backtrace noise backtrace_exclusion_patterns = [ - /spec_helper/, - /gems/ + %r{spec_helper}, + %r{gems}, ] if c.respond_to?(:backtrace_exclusion_patterns) @@ -108,33 +113,44 @@ def set_hieradata(hieradata) c.backtrace_clean_patterns = backtrace_exclusion_patterns end + # rubocop:disable RSpec/BeforeAfterAll c.before(:all) do - data = YAML.load(default_hiera_config) - data[:yaml][:datadir] = File.join(fixture_path, 'hieradata') + 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)) + 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.gsub(':','_')) + set_hieradata(hieradata.tr(':', '_')) elsif defined?(class_name) - set_hieradata(class_name.gsub(':','_')) + set_hieradata(class_name.tr(':', '_')) end end @@ -148,7 +164,7 @@ def set_hieradata(hieradata) Dir.glob("#{RSpec.configuration.module_path}/*").each do |dir| begin Pathname.new(dir).realpath - rescue - fail "ERROR: The module '#{dir}' is not installed. Tests cannot continue." + rescue StandardError + raise "ERROR: The module '#{dir}' is not installed. Tests cannot continue." end end