-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Add GitHub Action to enforce architect approval for strict version pins #44117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2f19d56
8cc3634
d0e5840
c2684be
45517b7
67cc772
a14b5c9
ee75f22
92f07da
f93a1ae
044c899
79fd49b
fdc7cc2
4addcdd
53b46af
8d799b9
2a41637
d98f9ff
02a98bb
e08dca5
73a5920
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,304 @@ | ||||||||
| #!/usr/bin/env python3 | ||||||||
| # cspell:ignore pyproject kashifkhan annatisch johanste | ||||||||
| """ | ||||||||
| Script to detect strict version pins (==) in Python package dependencies. | ||||||||
|
|
||||||||
| This script checks for new strict version pins introduced in setup.py and pyproject.toml | ||||||||
| files within the sdk directory, focusing on main runtime dependencies (install_requires | ||||||||
| for setup.py, [project] dependencies for pyproject.toml). | ||||||||
|
|
||||||||
| It ignores: | ||||||||
| - dev/test/extras dependencies | ||||||||
| - comments | ||||||||
| - Changes outside of main dependency sections | ||||||||
| """ | ||||||||
|
|
||||||||
| import json | ||||||||
| import os | ||||||||
| import re | ||||||||
| import subprocess | ||||||||
| import sys | ||||||||
| import urllib.request | ||||||||
| import urllib.parse | ||||||||
| from typing import Dict, List, Optional | ||||||||
|
||||||||
| from typing import Dict, List, Optional | |
| from typing import Dict, List |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The --diff-filter=AM flag only considers Added and Modified files, but excludes Renamed files (R). If a file is renamed that contains strict version pins, those changes would not be detected. Consider adding R to the filter (i.e., --diff-filter=AMR) to handle renamed files that may also contain modifications.
| "git", "diff", "--name-only", "--diff-filter=AM", | |
| "git", "diff", "--name-only", "--diff-filter=AMR", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the purposes of the functionality of this PR, I disagree.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Install eng/tools/azure-sdk-tools and use it to parse the directory of the changed pyproject.toml or setup.py. using ParsedSetup.from_path(). It should handle all edge cases. You don't need any of this extra code IMO.
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The bracket depth tracking logic has a potential issue when install_requires is detected on a line. On line 85, bracket_depth is reset to 0, but then on line 88, the bracket depth is calculated by counting brackets on the install_requires line. However, on line 97, brackets are counted again for all lines in the install_requires section, which could lead to double-counting if we've already counted brackets on line 88 for the same line.
Consider restructuring this to avoid double-counting: either don't count brackets on the install_requires line initially, or skip the bracket counting on line 97 for the first iteration after detecting install_requires.
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same issue as line 103 - the regex pattern will fail to match version specifiers with letters in them (pre-release, post-release, dev versions, etc.). Consider using a more comprehensive pattern to capture all valid PEP 440 version specifiers.
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The regex pattern r'["\']([^"\']+==[\d.]+[^"\']*)["\']' may not correctly match all valid version specifiers. Specifically, [\d.]+ only matches digits and dots, which excludes:
- Pre-release versions (e.g.,
package==1.0.0a1,package==1.0.0rc1) - Post-release versions (e.g.,
package==1.0.0.post1) - Dev versions (e.g.,
package==1.0.0.dev1) - Version strings with letters in them
Consider using a more comprehensive pattern like [\w.]+ or [^"\']+ (since you already have [^"\']* after the version) to capture all valid PEP 440 version specifiers.
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic for detecting TOML sections has a bug. On line 146, it checks if actual_line.startswith('[project]'), but this will fail to match [project] sections that have trailing content or are nested (e.g., [project.optional-dependencies]).
The current logic will incorrectly treat [project.optional-dependencies] as "not other section" because it starts with [project, but then it's supposed to be filtered out by line 154. However, if a line like [project.something-else] appears, it would be incorrectly treated as the main [project] section.
Consider changing line 146 to an exact match: if actual_line == '[project]': to ensure you're only matching the main [project] section header, not subsections.
| if actual_line.startswith('[project]'): | |
| if actual_line == '[project]': |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same issue as line 113 - the regex pattern r'["\']([^"\']+==[\d.]+[^"\']*)["\']' will fail to match version specifiers with letters in them (pre-release, post-release, dev versions, etc.). Consider using a more comprehensive pattern like [\w.]+ to capture all valid PEP 440 version specifiers.
| match = re.search(r'["\']([^"\']+==[\d.]+[^"\']*)["\']', actual_line) | |
| match = re.search(r'["\']([^"\']+==[\w.]+[^"\']*)["\']', actual_line) |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The authorization header format 'Authorization': f'token {github_token}' is using the older format. While this still works, GitHub recommends using the newer format 'Authorization': f'Bearer {github_token}' for consistency with OAuth 2.0 standards. Consider updating to the Bearer format for better alignment with current best practices.
| 'Authorization': f'token {github_token}', | |
| 'Authorization': f'Bearer {github_token}', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you bring in azure-sdk-tools you can also install the ghtools extra and get access to pygithub so you don't need to formulate the request yourself.
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment states "fail open to not block legitimate PRs if the API is down", but returning False when the approval check fails means the workflow will still block the PR (because the script checks if approval is false to exit with error code 1). If the intent is to "fail open" (allow the PR when API is down), this should return True instead. However, this may not be desirable from a security perspective as it could allow unapproved strict pins through if the API is temporarily unavailable.
Consider either:
- Changing the return to
Trueif you truly want to fail open - Updating the comment to reflect that this actually "fails closed" (blocks PRs when API is unavailable)
- Adding a more sophisticated retry mechanism or clear error messaging to help PR authors understand the API failure
| return False | |
| return True |
Copilot
AI
Dec 1, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The ::set-output command is deprecated by GitHub Actions. While the code correctly uses the newer GITHUB_OUTPUT file method as the primary approach (lines 233-237), the fallback on line 239 uses the deprecated syntax. This fallback is likely never executed in modern GitHub Actions runners, but for completeness, consider either removing this fallback or updating it to write to stdout without the deprecated syntax.
| print(f"::set-output name={name}::{value}") | |
| # Fallback: print a plain message for debugging (do not use deprecated syntax) | |
| print(f"[set_output fallback] {name}={value}") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| parameters: | ||
| ServiceDirectory: '' | ||
|
|
||
| steps: | ||
| - task: PythonScript@0 | ||
| displayName: 'Check for Strict Dependency Version Pins' | ||
| condition: and(succeededOrFailed(), eq(variables['Build.Reason'], 'PullRequest'), ne(variables['Skip.StrictPinCheck'], 'true')) | ||
| inputs: | ||
| scriptPath: 'scripts/devops_tasks/check_strict_pins.py' | ||
| arguments: '--base origin/$(System.PullRequest.TargetBranch) --head $(Build.SourceVersion) --pr-number $(System.PullRequest.PullRequestNumber)' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
urllib.parsemodule is imported but never used in the code. Consider removing this unused import for cleaner code.