From 35c9714a3bbaaa0fb2c3b8d08bdd8526035b6985 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Wed, 28 Jul 2021 12:44:03 -0700 Subject: [PATCH] Add GH action for verifying manifest.yml (#32) Add a GitHub Action to enable repositories like aws/aws-iot-embedded-device-c-sdk and FreeRTOS/FreeRTOS verify their manifest.yml files to ensure that all required submodule entires are present along with the latest commit ID information --- .github/workflows/test.yml | 26 +++++- README.md | 4 +- manifest-verifier/action.yml | 31 +++++++ manifest-verifier/requirements.txt | 2 + manifest-verifier/verify_manifest.py | 118 +++++++++++++++++++++++++++ 5 files changed, 178 insertions(+), 3 deletions(-) create mode 100644 manifest-verifier/action.yml create mode 100644 manifest-verifier/requirements.txt create mode 100644 manifest-verifier/verify_manifest.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ca119789..57b4a944 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,7 +37,7 @@ jobs: with: path: coreMQTT test-doxygen-zip-check: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 @@ -56,7 +56,7 @@ jobs: libs_parent_dir_path: libraries/standard,libraries/aws generate_zip: true test-doxygen-non-zip-check: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/checkout@v2 @@ -141,3 +141,25 @@ jobs: path: ./ exclude-dirs: complexity,formatting include-file-types: .c,.html + test-manifest-verifier: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup python environment + uses: actions/setup-python@v2 + with: + python-version: '3.x' + - name: Checkout the FreeRTOS/FreeRTOS repository for testing action on. + uses: actions/checkout@v2 + with: + repository: FreeRTOS/FreeRTOS + ref: '202107.00' + path: FreeRTOS + submodules: recursive + - name: Test manifest verifier + uses: ./manifest-verifier + with: + path: ./FreeRTOS + exclude-submodules: FreeRTOS-Plus/Test/CMock,FreeRTOS/Test/CMock/CMock,FreeRTOS/Test/litani + fail-on-incorrect-version: true + diff --git a/README.md b/README.md index 9cefbb88..11ed14cb 100644 --- a/README.md +++ b/README.md @@ -20,4 +20,6 @@ FreeRTOS libraries. library documentation. The memory estimates are generated by building the library with the ARM GCC toolchain. * **Link Verifier** - Verifies links present in source and Markdown files. Links verified include HTTP -URLs, and - for Markdown files - relative file path links and section anchors. +* **Manifest.yml Verifier** - Verifies that information of `manifest.yml` file matches the state of a repository for the presence of submodules and their commit IDs. + +URLs, and - for Markdown files - relative file path links and section anchors. \ No newline at end of file diff --git a/manifest-verifier/action.yml b/manifest-verifier/action.yml new file mode 100644 index 00000000..acbb98e7 --- /dev/null +++ b/manifest-verifier/action.yml @@ -0,0 +1,31 @@ +name: 'verify-manifest' +description: 'Verifies manifest.yml against missing submodule entires and stale version information' +inputs: + path: + description: 'Path to repository folder containing manifest.yml to verify.' + required: false + default: ./ + exclude-submodules: + description: 'List of comma-separated relative path to submodules that should not be present in manifest.yml. Eg libraries/thirdparty/tinycbor,libraries/thirdparty/mbedtls' + required: false + default: '' + fail-on-incorrect-version: + description: 'Boolean flag to indicate if verification should fail if any submodule version in manifest.yml file is incorrect or stale.' + required: false + default: 'false' +runs: + using: "composite" + steps: + - name: Install dependencies + run: pip install -r $GITHUB_ACTION_PATH/requirements.txt + shell: bash + - name: Run verifier script + working-directory: ${{ inputs.path }} + run: | + if [[ "${{ inputs.fail-on-incorrect-version }}" == "true" ]]; then + echo 'Value of flag is ${{ inputs.fail-on-incorrect-version }}' + python3 $GITHUB_ACTION_PATH/verify_manifest.py --ignore-submodule-path=${{ inputs.exclude-submodules }} --fail-on-incorrect-version + else + python3 $GITHUB_ACTION_PATH/verify_manifest.py --ignore-submodule-path=${{ inputs.exclude-submodules }} + fi + shell: bash diff --git a/manifest-verifier/requirements.txt b/manifest-verifier/requirements.txt new file mode 100644 index 00000000..f83cdd5a --- /dev/null +++ b/manifest-verifier/requirements.txt @@ -0,0 +1,2 @@ +pyyaml +gitpython \ No newline at end of file diff --git a/manifest-verifier/verify_manifest.py b/manifest-verifier/verify_manifest.py new file mode 100644 index 00000000..0389ed21 --- /dev/null +++ b/manifest-verifier/verify_manifest.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 + +import os, sys +from yaml import load +from yaml import CLoader as Loader + +from git import Repo +from argparse import ArgumentParser + +REPO_PATH='' + +# List of submodules excluded from manifest.yml file +IGNORE_SUBMODULES_LIST = [] + +# Obtain submodule path of all entries in manifest.yml file. +def read_manifest(): + dict = {} + + # Read YML file + path_manifest = os.path.join(REPO_PATH, 'manifest.yml') + assert os.path.exists(path_manifest), 'Missing manifest.yml' + with open(path_manifest, 'r') as fp: + manifest_data = fp.read() + yml = load(manifest_data, Loader=Loader) + assert 'dependencies' in yml, 'Manifest YML parsing error' + + # Iterate over all the "dependencies" entries, verify that + # each contains entries for the following hierarchy: + # name: "" + # version: "" + # repository: + # type: "git" + # url: + # path: + # + for dep in yml['dependencies']: + assert 'version' in dep, "Failed to parse 'version/tag' for submodule" + assert 'repository' in dep and 'path' in dep['repository'] and 'url' in dep['repository'], "Failed to parse 'repository' object for submodule" + dict[dep['name']] = (dep['repository']['path'], dep['version']) + + return dict + +# Generate list of submodules path in repository, excluding the +# path in IGNORES_SUBMODULES_LIST. +def get_all_submodules(): + info_dict = {} + repo = Repo(REPO_PATH) + for submodule in repo.submodules: + path = submodule.abspath.replace(REPO_PATH+'/', '') + if path not in IGNORE_SUBMODULES_LIST: + #print(path,':',submodule.module().head.commit) + info_dict[path] = submodule.module().head.commit + + return info_dict + +if __name__ == '__main__': + parser = ArgumentParser(description='manifest.yml verifier') + parser.add_argument('--repo-root-path', + type=str, + required=None, + default=os.getcwd(), + help='Path to the repository root.') + parser.add_argument('--ignore-submodule-path', + type=str, + required=None, + help='Comma-separated list of submodules path to ignore.') + parser.add_argument('--fail-on-incorrect-version', + action='store_true', + help='Flag to indicate script to fail for incorrect submodules versions in manifest.yml') + + args = parser.parse_args() + + if args.ignore_submodule_path != None: + IGNORE_SUBMODULES_LIST = args.ignore_submodule_path.split(',') + + # Convert any relative path (like './') in passed argument to absolute path. + REPO_PATH = os.path.abspath(args.repo_root_path) + + submodules_info_from_manifest = read_manifest() + submodule_path_from_manifest = [pair[0] for pair in submodules_info_from_manifest.values()] + submodule_path_from_manifest = sorted(submodule_path_from_manifest) + + submodules_info_from_git = get_all_submodules() + submodule_path_from_git = sorted(submodules_info_from_git.keys()) + + print(REPO_PATH) + print('\nList of submoduled libraries being verified:', submodule_path_from_git) + + # Check that manifest.yml contains entries for all submodules + # present in repository. + if submodule_path_from_manifest != submodule_path_from_git: + print('manifest.yml is missing entries for:') + # Find list of library submodules missing in manifest.yml + for git_path in submodule_path_from_git: + if git_path not in submodule_path_from_manifest: + print(git_path) + sys.exit(1) + + # Verify that manifest contains correct versions of submodules pointers. + mismatch_flag = False + print('\nVerifying that manifest.yml versions are up-to-date.....') + for submodule_name, submodule_info in submodules_info_from_manifest.items(): + relative_path = submodule_info[0] + manifest_commit = submodule_info[1] + submodule = Repo(REPO_PATH+'/'+relative_path) + submodule.remote('origin').fetch() + submodule.git.checkout(manifest_commit) + if (submodules_info_from_git[relative_path] != submodule.head.commit): + print('manifest.yml does not have correct commit ID for', submodule_name,'manifest Commit=(',manifest_commit, submodule.head.commit,') Actual Commit=',submodules_info_from_git[relative_path]) + mismatch_flag = True + + if mismatch_flag and args.fail_on_incorrect_version: + sys.exit(1) + + print('\nmanifest.yml file has been verified!') + sys.exit(0) + +