From ea23cd86904f435150cf59a2ece4f048c47b448b Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Tue, 21 Jul 2026 16:37:15 -0500 Subject: [PATCH 1/2] Check for category existence on main --- .github/scripts/check_profile_categories.py | 85 +++++++++++++++++++ .../check-profile-categories-comment.yml | 34 ++++++++ .../workflows/check-profile-categories.yml | 48 +++++++++++ 3 files changed, 167 insertions(+) create mode 100644 .github/scripts/check_profile_categories.py create mode 100644 .github/workflows/check-profile-categories-comment.yml create mode 100644 .github/workflows/check-profile-categories.yml diff --git a/.github/scripts/check_profile_categories.py b/.github/scripts/check_profile_categories.py new file mode 100644 index 0000000000..5fd0d96106 --- /dev/null +++ b/.github/scripts/check_profile_categories.py @@ -0,0 +1,85 @@ +import csv +import os +import sys +import yaml +from pathlib import Path + +cwd = os.getcwd() +missing_category_profiles = [] +deleted_profiles = [] + +with open(str(Path.home()) + '/files.csv', 'r') as csvfile: + csvreader = csv.reader(csvfile) + changed_files = next(csvreader) + + for file in changed_files: + file_basename = os.path.basename(file) + file_directory = os.path.dirname(file) + + if '/profiles/' in file and file.endswith('.yml'): + print('\nCHECKING PROFILE:\n%s' % file) + + os.chdir(file_directory) + + if not os.path.exists(file_basename): + print("Skipping %s - file was deleted" % file_basename) + deleted_profiles.append(file) + os.chdir(cwd) + continue + + with open(file_basename) as fp: + try: + profile = yaml.safe_load(fp) + except yaml.YAMLError as e: + print("Error parsing %s: %s" % (file_basename, e)) + os.chdir(cwd) + continue + + if not profile or 'components' not in profile: + print("Skipping %s - no components found" % file_basename) + os.chdir(cwd) + continue + + # Find the main component and verify it has a categories field + main_component = next( + (c for c in profile['components'] if c.get('id') == 'main'), + None + ) + + if main_component is None: + print("Warning: %s has no 'main' component" % file_basename) + os.chdir(cwd) + continue + + if not main_component.get('categories'): + print("MISSING CATEGORY: %s" % file) + missing_category_profiles.append(file) + else: + print("OK: %s has categories: %s" % ( + file_basename, + [c['name'] for c in main_component['categories']] + )) + + os.chdir(cwd) + +with open("profile-categories-comment-body.md", "w") as f: + if missing_category_profiles: + f.write("Profile category check: :x: **Missing categories detected.**\n\n") + f.write("The following profiles are missing a `categories` field on the `main` component:\n\n") + for profile in missing_category_profiles: + f.write("- `%s`\n" % profile) + f.write("\nPlease add a `categories` entry to the `main` component. Example:\n") + f.write("```yaml\ncomponents:\n - id: main\n categories:\n - name: Switch\n capabilities:\n ...\n```\n") + else: + f.write("Profile category check: :white_check_mark: Passed - all profiles have a category defined.\n") + + if deleted_profiles: + f.write("\n:warning: **Deleted profile files detected:**\n") + for deleted in deleted_profiles: + f.write("- `%s`\n" % deleted) + +with open("profile-categories-comment-body.md", "r") as f: + print("\n" + f.read()) + +if missing_category_profiles: + sys.exit(1) diff --git a/.github/workflows/check-profile-categories-comment.yml b/.github/workflows/check-profile-categories-comment.yml new file mode 100644 index 0000000000..cf207144ef --- /dev/null +++ b/.github/workflows/check-profile-categories-comment.yml @@ -0,0 +1,34 @@ +name: Post profile categories comment +on: + workflow_run: + workflows: [Check profile categories] + types: + - completed + +jobs: + comment-on-pr: + runs-on: ubuntu-latest + steps: + - name: Download comment artifact + uses: dawidd6/action-download-artifact@v6 + with: + workflow: check-profile-categories.yml + run_id: ${{ github.event.workflow_run.id }} + + - run: echo "pr_number=$(cat pr_number/pr_number.txt)" >> $GITHUB_ENV + + - name: Find comment + uses: peter-evans/find-comment@v2 + id: fc + with: + body-includes: Profile category check + comment-author: 'github-actions[bot]' + issue-number: ${{ env.pr_number }} + + - name: Post comment + uses: peter-evans/create-or-update-comment@v2 + with: + comment-id: ${{ steps.fc.outputs.comment-id }} + body-file: 'profile_categories_comment/profile-categories-comment-body.md' + edit-mode: replace + issue-number: ${{ env.pr_number }} diff --git a/.github/workflows/check-profile-categories.yml b/.github/workflows/check-profile-categories.yml new file mode 100644 index 0000000000..1321335c1d --- /dev/null +++ b/.github/workflows/check-profile-categories.yml @@ -0,0 +1,48 @@ +name: Check profile categories +on: + pull_request: + types: [opened, synchronize] + paths: + - 'drivers/**/profiles/*.yml' + +jobs: + check-profile-categories: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + # Creates file "~/files.csv", among others + - id: file_changes + name: Gather file changes + uses: trilom/file-changes-action@1.2.4 + with: + output: ',' + fileOutput: ',' + + # For verification + - name: Show files changed + run: | + cat $HOME/files.csv + + - name: Run python script + run: | + python ./.github/scripts/check_profile_categories.py + + - name: Upload profile categories comment artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: profile_categories_comment + path: | + profile-categories-comment-body.md + + - run: echo ${{ github.event.number }} > pr_number.txt + + - name: Upload PR info + if: always() + uses: actions/upload-artifact@v4 + with: + name: pr_number + path: | + pr_number.txt From 6b4d7dddefc353617258690df211e91788e251da Mon Sep 17 00:00:00 2001 From: Harrison Carter Date: Mon, 27 Jul 2026 13:03:34 -0500 Subject: [PATCH 2/2] Update scripts from trilom file gen to tj-actions env gen --- .github/scripts/check_duplicates.py | 89 +++++++++---------- .github/scripts/check_profile_categories.py | 89 +++++++++---------- .../workflows/check-profile-categories.yml | 17 ++-- .github/workflows/duplicate-profiles.yml | 17 ++-- 4 files changed, 96 insertions(+), 116 deletions(-) diff --git a/.github/scripts/check_duplicates.py b/.github/scripts/check_duplicates.py index 65e2ae2ea6..e335f6083a 100644 --- a/.github/scripts/check_duplicates.py +++ b/.github/scripts/check_duplicates.py @@ -1,4 +1,3 @@ -import csv import os import yaml from pathlib import Path @@ -101,51 +100,49 @@ def compare_components(prof1, prof2): return True -with open(str(Path.home()) + '/files.csv', 'r') as csvfile: - csvreader = csv.reader(csvfile) - changed_files = next(csvreader) - - for file in changed_files: - file_basename = os.path.basename(file) - file_directory = os.path.dirname(file) - - if '/profiles/' in file: - print('\nNEW PROFILE:\n%s is a profile! Comparing to other profiles...' % file) - - os.chdir(file_directory) - new_profile = file_basename - - # Skip deleted files and track them for warning - if not os.path.exists(new_profile): - print("Skipping %s - file was deleted" % new_profile) - deleted_profiles.append(file) - os.chdir(cwd) - continue - - for current_profile in os.listdir("./"): - # compare to YAML files that are not the same file - # Compare only .yml files and only files that have not already been found to be a duplicate - if current_profile != new_profile and Path(current_profile).suffix == ".yml" and (current_profile, new_profile) not in duplicate_pairs: - print("Comparing %s vs %s" % (new_profile, current_profile)) - with open(new_profile) as new_data, open(current_profile) as current_data: - new_profile_map = yaml.safe_load(new_data) - current_profile_map = yaml.safe_load(current_data) - - ''' Compare profiles. A duplicate is defined as follows: - - categories must be the same - - capabilities must be the same, with some ordering restrictions - - top capability must match, but subsequent ordering does not matter - - embedded configs must be the same, but certain values can be ordered differently (i.e. enabledValues) - - preferences must be the same - ''' - if(compare_preferences(new_profile_map, current_profile_map) == True and - compare_metadata(new_profile_map, current_profile_map) == True and - compare_components(new_profile_map, current_profile_map) == True): - print("%s and %s are duplicates!\n" % (new_profile, current_profile)) - duplicate_pairs.append((new_profile, current_profile)) - - # return to original directory - os.chdir(cwd) +changed_files = os.environ.get('ALL_CHANGED_FILES', '').split() + +for file in changed_files: + file_basename = os.path.basename(file) + file_directory = os.path.dirname(file) + + if '/profiles/' in file: + print('\nNEW PROFILE:\n%s is a profile! Comparing to other profiles...' % file) + + os.chdir(file_directory) + new_profile = file_basename + + # Skip deleted files and track them for warning + if not os.path.exists(new_profile): + print("Skipping %s - file was deleted" % new_profile) + deleted_profiles.append(file) + os.chdir(cwd) + continue + + for current_profile in os.listdir("./"): + # compare to YAML files that are not the same file + # Compare only .yml files and only files that have not already been found to be a duplicate + if current_profile != new_profile and Path(current_profile).suffix == ".yml" and (current_profile, new_profile) not in duplicate_pairs: + print("Comparing %s vs %s" % (new_profile, current_profile)) + with open(new_profile) as new_data, open(current_profile) as current_data: + new_profile_map = yaml.safe_load(new_data) + current_profile_map = yaml.safe_load(current_data) + + ''' Compare profiles. A duplicate is defined as follows: + - categories must be the same + - capabilities must be the same, with some ordering restrictions + - top capability must match, but subsequent ordering does not matter + - embedded configs must be the same, but certain values can be ordered differently (i.e. enabledValues) + - preferences must be the same + ''' + if(compare_preferences(new_profile_map, current_profile_map) == True and + compare_metadata(new_profile_map, current_profile_map) == True and + compare_components(new_profile_map, current_profile_map) == True): + print("%s and %s are duplicates!\n" % (new_profile, current_profile)) + duplicate_pairs.append((new_profile, current_profile)) + + # return to original directory + os.chdir(cwd) with open("profile-comment-body.md", "w") as f: if duplicate_pairs: diff --git a/.github/scripts/check_profile_categories.py b/.github/scripts/check_profile_categories.py index 5fd0d96106..31609cbe7e 100644 --- a/.github/scripts/check_profile_categories.py +++ b/.github/scripts/check_profile_categories.py @@ -1,4 +1,3 @@ -import csv import os import sys import yaml @@ -8,59 +7,57 @@ missing_category_profiles = [] deleted_profiles = [] -with open(str(Path.home()) + '/files.csv', 'r') as csvfile: - csvreader = csv.reader(csvfile) - changed_files = next(csvreader) +changed_files = os.environ.get('ALL_CHANGED_FILES', '').split() - for file in changed_files: - file_basename = os.path.basename(file) - file_directory = os.path.dirname(file) +for file in changed_files: + file_basename = os.path.basename(file) + file_directory = os.path.dirname(file) - if '/profiles/' in file and file.endswith('.yml'): - print('\nCHECKING PROFILE:\n%s' % file) + if '/profiles/' in file and file.endswith('.yml'): + print('\nCHECKING PROFILE:\n%s' % file) - os.chdir(file_directory) + os.chdir(file_directory) - if not os.path.exists(file_basename): - print("Skipping %s - file was deleted" % file_basename) - deleted_profiles.append(file) - os.chdir(cwd) - continue - - with open(file_basename) as fp: - try: - profile = yaml.safe_load(fp) - except yaml.YAMLError as e: - print("Error parsing %s: %s" % (file_basename, e)) - os.chdir(cwd) - continue - - if not profile or 'components' not in profile: - print("Skipping %s - no components found" % file_basename) - os.chdir(cwd) - continue + if not os.path.exists(file_basename): + print("Skipping %s - file was deleted" % file_basename) + deleted_profiles.append(file) + os.chdir(cwd) + continue - # Find the main component and verify it has a categories field - main_component = next( - (c for c in profile['components'] if c.get('id') == 'main'), - None - ) - - if main_component is None: - print("Warning: %s has no 'main' component" % file_basename) + with open(file_basename) as fp: + try: + profile = yaml.safe_load(fp) + except yaml.YAMLError as e: + print("Error parsing %s: %s" % (file_basename, e)) os.chdir(cwd) continue - if not main_component.get('categories'): - print("MISSING CATEGORY: %s" % file) - missing_category_profiles.append(file) - else: - print("OK: %s has categories: %s" % ( - file_basename, - [c['name'] for c in main_component['categories']] - )) - - os.chdir(cwd) + if not profile or 'components' not in profile: + print("Skipping %s - no components found" % file_basename) + os.chdir(cwd) + continue + + # Find the main component and verify it has a categories field + main_component = next( + (c for c in profile['components'] if c.get('id') == 'main'), + None + ) + + if main_component is None: + print("Warning: %s has no 'main' component" % file_basename) + os.chdir(cwd) + continue + + if not main_component.get('categories'): + print("MISSING CATEGORY: %s" % file) + missing_category_profiles.append(file) + else: + print("OK: %s has categories: %s" % ( + file_basename, + [c['name'] for c in main_component['categories']] + )) + + os.chdir(cwd) with open("profile-categories-comment-body.md", "w") as f: if missing_category_profiles: diff --git a/.github/workflows/check-profile-categories.yml b/.github/workflows/check-profile-categories.yml index 1321335c1d..6340175810 100644 --- a/.github/workflows/check-profile-categories.yml +++ b/.github/workflows/check-profile-categories.yml @@ -12,20 +12,13 @@ jobs: - name: Checkout uses: actions/checkout@v3 - # Creates file "~/files.csv", among others - - id: file_changes - name: Gather file changes - uses: trilom/file-changes-action@1.2.4 - with: - output: ',' - fileOutput: ',' - - # For verification - - name: Show files changed - run: | - cat $HOME/files.csv + - name: Gather file changes + id: changed-files + uses: tj-actions/changed-files@v47 - name: Run python script + env: + ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} run: | python ./.github/scripts/check_profile_categories.py diff --git a/.github/workflows/duplicate-profiles.yml b/.github/workflows/duplicate-profiles.yml index ae524cc1c2..5f66269cc1 100644 --- a/.github/workflows/duplicate-profiles.yml +++ b/.github/workflows/duplicate-profiles.yml @@ -13,20 +13,13 @@ jobs: - name: Checkout uses: actions/checkout@v3 - # Creates file "$/files.csv", among others - - id: file_changes - name: Gather file changes - uses: trilom/file-changes-action@1.2.4 - with: - output: ',' - fileOutput: ',' - - # For verification - - name: Show files changed - run: | - cat $HOME/files.csv + - name: Gather file changes + id: changed-files + uses: tj-actions/changed-files@v47 - name: Run python script + env: + ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} run: | python ./.github/scripts/check_duplicates.py