Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions .github/workflows/gate-lint-ansible-roles.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
name: Gate (AR / RHEL)
on:
pull_request:
branches: [ 'master' ]
branches: ['master']
permissions:
contents: read
env:
COLLECTION_NAMESPACE: redhat
COLLECTION_NAME: rhel_hardening_roles
concurrency:
group: ${{ github.workflow }}-${{ github.event.number || github.run_id }}
cancel-in-progress: true
Expand All @@ -30,4 +33,24 @@ jobs:
- name: Set git safe directory
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Lint Ansible Roles
run: ansible-lint -x 204 -x experimental -x command-instead-of-module ./build/ansible_roles/*
run: ansible-lint -x 204 -x experimental ./build/ansible_roles/*
- name: Build Ansible Collection (as published downstream)
# --target hub selects the Automation Hub namespace (redhat); there is no
# standalone --namespace flag.
run: PYTHONPATH=. python3 utils/ansible_roles_to_collection.py --roles-dir ./build/ansible_roles --output-dir ./build/ansible_collection --target hub --collection ${{ env.COLLECTION_NAME }}
# The published collection enforces ansible-lint's command-instead-of-module
# rule for Automation Hub certification. Every command/shell remediation
# with a module equivalent is rewritten to the vendored ssg_hardening module
# at assembly time; a new upstream remediation whose command is not mapped
# would regress certification. Lint the assembled collection with just that
# rule so the regression fails this pull request instead of surfacing only
# at publishing time.
# --offline keeps the lint hermetic: the collection is already available via
# ANSIBLE_COLLECTIONS_PATH, so ansible-lint has nothing to install. Without
# it, ansible-lint installs a copy of the collection being linted into its
# cache and then reports "Another version of 'redhat.rhel_hardening_roles'
# ... was found installed", because the same collection is also on the
# collections path it was given.
- name: Gate collection on command-instead-of-module (Automation Hub cert rule)
run: ANSIBLE_COLLECTIONS_PATH="$GITHUB_WORKSPACE/build/ansible_collection" ansible-lint --offline --nocolor --tags command-instead-of-module roles/
working-directory: ./build/ansible_collection/ansible_collections/${{ env.COLLECTION_NAMESPACE }}/${{ env.COLLECTION_NAME }}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
option: gpgcheck
value: '1'
no_extra_spaces: True
mode: '0644'
owner: root
# regex filters grep output for files ending in .repo and matching section names.
loop: "{{ repo_grep_results.stdout | regex_findall( '(.+\\.repo):\\[(.+)\\]\\n?' ) if repo_grep_results is not skipped else [] }}"
when: repo_grep_results is not skipped
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
value: '1'
no_extra_spaces: True
mode: '0644'
owner: root
# regex filters grep output for files ending in .repo and matching section names.
loop: "{{ repo_grep_results.stdout |regex_findall( '(.+\\.repo):\\[(.+)\\]\\n?' ) if repo_grep_results is not skipped else [] }}"
when: repo_grep_results is not skipped
6 changes: 0 additions & 6 deletions tests/ansible-lint_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,7 @@ offline: true

skip_list:
- '204' # Lines should be no longer than 160 chars
- '301' # Commands should not change things if nothing needs doing
- '303' # Using command rather than module
- '403' # Package installs should not use latest
- '503' # Tasks that run when changed should likely be handlers
- no-tabs # Rule permissions_local_var_log uses explicit \t character
- command-instead-of-module # sometimes systemctl must be executed instead of the module
- risky-file-permissions # Almost all rules do not set perms
- yaml # There is no use of using ansible-lint to run yamllint as yamllint has own test
- fqcn[action-core] # Most playbooks use old format currently, so skip for now.
- fqcn[action] # ditto
Expand Down
78 changes: 78 additions & 0 deletions utils/test_ansible_collection.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/bin/bash
# Test script: build Ansible roles then generate an Ansible collection.
#
# Usage: utils/test_ansible_collection.sh [OUTPUT_DIR] [PRODUCT] [TARGET]
# OUTPUT_DIR destination for the generated collection (default: /tmp/test-collection)
# PRODUCT ComplianceAsCode product to build roles for (default: rhel9)
# TARGET Collection registry target: galaxy (namespace redhatofficial) or
# hub (namespace redhat) (default: galaxy)
set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SSG_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
OUTPUT_DIR="${1:-/tmp/test-collection}"
PRODUCT="${2:-rhel9}"
TARGET="${3:-galaxy}"
BUILD_DIR="${SSG_ROOT}/build"

echo "=== Step 1: Configure and build ${PRODUCT} with Ansible roles ==="
cd "${SSG_ROOT}"

# Use build_product only for the cmake configuration step; the --datastream flag
# limits what gets built so we follow up with an explicit roles target below.
ADDITIONAL_CMAKE_OPTIONS="-DSSG_ANSIBLE_ROLES_ENABLED=TRUE" \
./build_product "${PRODUCT}" --datastream

echo ""
echo "=== Step 2: Build Ansible roles target ==="
cd "${BUILD_DIR}"
ninja "generate-$(echo "${PRODUCT}" | tr '[:upper:]' '[:lower:]')-ansible-roles"
cd "${SSG_ROOT}"

echo ""
echo "=== Step 3: Generate Ansible collection ==="
rm -rf "${OUTPUT_DIR}"
python3 utils/ansible_roles_to_collection.py \
--roles-dir "${BUILD_DIR}/ansible_roles" \
--output-dir "${OUTPUT_DIR}" \
--target "${TARGET}" \
--build

echo ""
echo "=== Step 4: Verify output ==="
# --target selects the namespace (galaxy -> redhatofficial, hub -> redhat); there
# is no standalone --namespace flag anymore.
case "${TARGET}" in
hub) NAMESPACE="redhat" ;;
galaxy|*) NAMESPACE="redhatofficial" ;;
esac
COLLECTION_DIR="${OUTPUT_DIR}/ansible_collections/${NAMESPACE}/rhel_hardening_roles"

echo "--- Vendored modules ---"
ls "${COLLECTION_DIR}/plugins/modules/"

echo ""
echo "--- FQCN rewrite check ---"
STATUS=0
if grep -rq "community\.general\.\|ansible\.posix\." "${COLLECTION_DIR}/roles/" 2>/dev/null; then
echo "FAIL: unrewritten FQCNs found:"
grep -rn "community\.general\.\|ansible\.posix\." "${COLLECTION_DIR}/roles/"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it exit with 1 and/or save the exit code to exit with 1? It seems that it will print FAIL but finish successfully.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be fixed by:

Make utils/test_ansible_collection.sh return a non-zero exit status when the
FQCN rewrite check finds unrewritten references, instead of printing FAIL and
finishing successfully.

STATUS=1
else
echo "OK: no unrewritten FQCNs in roles"
fi

echo ""
echo "--- galaxy.yml ---"
python3 -c "
import yaml
d = yaml.safe_load(open('${COLLECTION_DIR}/galaxy.yml'))
print(' namespace:', d['namespace'])
print(' name: ', d['name'])
print(' version: ', d['version'])
"

echo ""
echo "=== Done ==="
echo "Artifact: $(ls "${OUTPUT_DIR}"/"${NAMESPACE}"-rhel_hardening_roles-*.tar.gz 2>/dev/null || echo 'not found')"
exit "${STATUS}"
Loading