diff --git a/.github/workflows/compare-ds.yaml b/.github/workflows/compare-ds.yaml index f642a2b5c29..809dc512376 100644 --- a/.github/workflows/compare-ds.yaml +++ b/.github/workflows/compare-ds.yaml @@ -138,3 +138,35 @@ jobs: type: delete comment_id: ${{ steps.fc.outputs.comment-id }} token: ${{ secrets.GITHUB_TOKEN }} + - name: Compare Ansible playbook shell commands + if: ${{ steps.ctf.outputs.CTF_OUTPUT_SIZE != '0' }} + run: utils/ansible_shell_diff.py ssg-${{steps.product.outputs.prop}}-ds.xml build/ssg-${{steps.product.outputs.prop}}-ds.xml | tee diff.log + env: + PYTHONPATH: ${{ github.workspace }} + - name: Test if there are Ansible shell module changes + if: ${{ steps.ctf.outputs.CTF_OUTPUT_SIZE != '0' }} + run: echo "SHELL_DIFF_OUTPUT_SIZE=$(stat --printf="%s" diff.log)" >> $GITHUB_OUTPUT + id: ansible_shell_diff + - name: Find Comment + uses: peter-evans/find-comment@v3 + id: shell_diff + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: Change in Ansible 'shell' module found. + - name: Create comment + if: ${{ steps.ansible_shell_diff.outputs.SHELL_DIFF_OUTPUT_SIZE != '0' && steps.shell_diff.outputs.comment-id == 0 }} + uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ github.event.pull_request.number }} + body: | + Change in Ansible `shell` module found. + + Please consider using more suitable Ansible module than `shell` if possible. + - name: Delete existing comment in case new commits trigger no changes in Ansible shell module + if: ${{ (steps.ansible_shell_diff.outputs.SHELL_DIFF_OUTPUT_SIZE == '0' || steps.ctf.outputs.CTF_OUTPUT_SIZE == '0') && steps.shell_diff.outputs.comment-id != 0 }} + uses: jungwinter/comment@v1 + with: + type: delete + comment_id: ${{ steps.shell_diff.outputs.comment-id }} + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/utils/ansible_shell_diff.py b/utils/ansible_shell_diff.py new file mode 100755 index 00000000000..5943d6149a6 --- /dev/null +++ b/utils/ansible_shell_diff.py @@ -0,0 +1,61 @@ +#!/usr/bin/python3 + +import yaml +import argparse +import subprocess +from pathlib import Path + + +def parse_args(): + parser = argparse.ArgumentParser( + description="Check for changes in 'shell' module usage in two datastreams" + ) + parser.add_argument('old', metavar='OLD_DS_PATH', help="Path to old datastream") + parser.add_argument('new', metavar='NEW_DS_PATH', help="Path to new datastream") + return parser.parse_args() + + +def get_shell_tasks(tasks): + """ + Find all shell/command module tasks. + Module can be as FQCN or just short name. + Both forms, free form and 'cmd:' parameter, are supported. + """ + shell_tasks = [] + for task in tasks: + for task_name in ['shell', 'ansible.builtin.shell', 'command', 'ansible.builtin.command']: + if task_name in task: + if type(task[task_name]) is dict and 'cmd' in task[task_name]: + shell_tasks.append(task[task_name]['cmd']) + else: + shell_tasks.append(task[task_name]) + # Search also blocks + if 'block' in task: + tasks = get_shell_tasks(task['block']) + if tasks: + shell_tasks.extend(tasks) + return shell_tasks + + +if __name__ == '__main__': + args = parse_args() + # Generate old and new playbooks for all rules in datastream + cmd = ['oscap', 'xccdf', 'generate', 'fix', '--profile', '(all)', '--fix-type', 'ansible'] + subprocess.run(cmd + ['--output', 'old.yml', args.old], check=True) + subprocess.run(cmd + ['--output', 'new.yml', args.new], check=True) + + with open('old.yml', 'r') as old_yaml, open('new.yml', 'r') as new_yaml: + old_playbook = yaml.safe_load(old_yaml) + new_playbook = yaml.safe_load(new_yaml) + # Get only shell commands + old_shell_modules = get_shell_tasks(old_playbook[0]['tasks']) + new_shell_modules = get_shell_tasks(new_playbook[0]['tasks']) + + diff = set(new_shell_modules) - set(old_shell_modules) + if diff: + print(f"Changes in Ansible shell module have been found:") + print("\n".join(diff)) + + Path.unlink('old.yml') + Path.unlink('new.yml') + exit(0)