Feature: Enable cross-organization project access with GH_PROJECT_ONLY_TOKEN#16
Conversation
There was a problem hiding this comment.
Pull request overview
Enables cross-organization access to GitHub Projects V2 for the priority-sync feature by allowing GraphQL calls to authenticate with a dedicated PAT (GH_PROJECT_ONLY_TOKEN) while keeping the rest of the pipeline on the default github.token.
Changes:
- Add optional
envpassthrough torun_cmd/run_gh, and use it in Projects GraphQL calls to temporarily overrideGH_TOKENwhenGH_PROJECT_ONLY_TOKENis present. - Update reusable workflow + example caller to request
repository-projects: writeand forwardGH_PROJECT_ONLY_TOKEN. - Document cross-org project token setup and troubleshooting guidance.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
src/shared/github_projects.py |
Uses GH_PROJECT_ONLY_TOKEN (when set) to authenticate gh api graphql calls for cross-org ProjectV2 access. |
src/shared/common.py |
Extends subprocess helpers to accept an optional environment mapping. |
docs/security/security.md |
Adds documentation and troubleshooting for cross-org ProjectV2 token usage. |
docs/security/example_workflows/aquasec-night-scan.yml |
Updates required permissions and forwards the optional project-only token secret. |
.github/workflows/aquasec-scan.yml |
Updates permissions and adds GH_PROJECT_ONLY_TOKEN as a workflow-call secret forwarded into the sync step env. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
tmikula-dev
left a comment
There was a problem hiding this comment.
The logic looks correct and should work. I have mainly issue with the naming and overwhelming information that are not needed for better clean code and also clear documentation.
| # Required only when project-org differs from this repository's org. | ||
| # See docs/security/security.md – "Cross-org project token" for how to create it. |
There was a problem hiding this comment.
| # Required only when project-org differs from this repository's org. | |
| # See docs/security/security.md – "Cross-org project token" for how to create it. | |
| # Required only when project-org differs from AbsaOSS. | |
| # See docs/security/security.md – "Cross-org project token" for how to create it. |
If it really is about differing from THIS repository's org, it is always AbsaOSS.
| GH_PROJECT_ONLY_TOKEN: | ||
| description: > | ||
| Classic PAT with 'project' scope on an account that is a member of the | ||
| org that owns the ProjectV2 board. Required only when the project lives | ||
| in a different organisation than the calling repository. When omitted, | ||
| github.token is used (works only for same-org projects). |
There was a problem hiding this comment.
I am not a big fan of the naming, what only stands for in this case? What about ORG_PROJECT_PAT,CROSS_ORG_PROJECT_TOKEN or something like this. Then in the description I would capitalize the word ONLY, to catch an eye.
|
|
||
| ##### Cross-org project token | ||
|
|
||
| When `project-org` is a **different organisation** than the one that owns the calling repository, the automatic `github.token` cannot resolve the ProjectV2 board and you will see: |
There was a problem hiding this comment.
| When `project-org` is a **different organisation** than the one that owns the calling repository, the automatic `github.token` cannot resolve the ProjectV2 board and you will see: | |
| When `project-org` is in a **different organisation** than the AbsaOSS, the automatic `github.token` cannot resolve the ProjectV2 board and you will see: |
| TEAMS_WEBHOOK_URL: ${{ secrets.TEAMS_WEBHOOK_URL }} | ||
| ``` | ||
|
|
||
| ##### Cross-org project token |
There was a problem hiding this comment.
Nitpicking: having a 5th level header for this big chapter is not correct. Should this doc be refactored in the future less needed PR.
|
|
||
| The fix is a **Personal Access Token (classic)** with the `project` scope, created by an account that is a member of the org owning the project board. | ||
|
|
||
| > **Why classic and not fine-grained?** Fine-grained PATs require the org admin to explicitly allow them under org settings. If that is not enabled, a classic PAT with the `project` scope is the practical alternative. Classic PATs are less granular (they apply to all orgs the account belongs to) but the `project` scope is the minimum checkbox needed and grants no code or issue access. |
There was a problem hiding this comment.
Do we need to overwhelm people with information why it is not working?
| The example caller workflow already passes this secret: | ||
|
|
||
| ```yaml | ||
| secrets: | ||
| GH_PROJECT_ONLY_TOKEN: ${{ secrets.GH_PROJECT_ONLY_TOKEN }} | ||
| ``` | ||
|
|
||
| The reusable workflow forwards it to the Python script as the `GH_PROJECT_ONLY_TOKEN` environment variable. The script uses it **only** for ProjectV2 GraphQL calls; all other operations (issue list/create/update) continue to use the scoped `github.token`. | ||
|
|
||
| **Minimum required scope summary** | ||
|
|
||
| | Scope | Reason | | ||
| | --- | --- | | ||
| | `project` | Read/write access to org-level ProjectV2 — query metadata + set Priority field values | | ||
| | *(everything else)* | Not needed — do not check | | ||
|
|
There was a problem hiding this comment.
I would keep the docs as little as needed to hand over the information to the final consumer.
| """Execute a GraphQL query via ``gh api graphql`` and return parsed JSON. | ||
|
|
||
| When ``GH_PROJECT_ONLY_TOKEN`` is set in the environment the GraphQL call is made | ||
| with that token instead of the default ``GH_TOKEN``. This allows cross-org | ||
| project access while the rest of the pipeline continues to use the scoped | ||
| ``github.token``. | ||
| """ |
There was a problem hiding this comment.
| """Execute a GraphQL query via ``gh api graphql`` and return parsed JSON. | |
| When ``GH_PROJECT_ONLY_TOKEN`` is set in the environment the GraphQL call is made | |
| with that token instead of the default ``GH_TOKEN``. This allows cross-org | |
| project access while the rest of the pipeline continues to use the scoped | |
| ``github.token``. | |
| """ | |
| """Execute a GraphQL query via ``gh api graphql`` and return parsed JSON. | |
| When ``GH_PROJECT_ONLY_TOKEN`` is set in the environment the GraphQL call is made | |
| with that token instead of the default ``GH_TOKEN``. | |
| """ |
| env: Mapping[str, str] | None = None | ||
| project_token = os.environ.get("GH_PROJECT_ONLY_TOKEN", "") | ||
| if project_token: | ||
| env = {**os.environ, "GH_TOKEN": project_token} |
There was a problem hiding this comment.
If this is the specific env for some scenario, we should rename this env.
| cmd = ["gh"] + args | ||
| try: | ||
| return run_cmd(cmd, capture_output=capture_output) | ||
| return run_cmd(cmd, capture_output=capture_output, env=env) |
There was a problem hiding this comment.
Again, what is the cmd? I would rename the cmd, the env here as well.
| args += ["-F", f"{k}={v}"] | ||
| res = run_gh(args) | ||
| env: Mapping[str, str] | None = None | ||
| project_token = os.environ.get("GH_PROJECT_ONLY_TOKEN", "") |
There was a problem hiding this comment.
This is some specific token, idea is to have GH_PROJECT_ONLY_TOKEN (hope renamed) will be 1:1 with this current project_token. It is specific project token for this situation.
I will keep your review comment for following PR as I would like to check if proposed solution even work before beautifing. |
tmikula-dev
left a comment
There was a problem hiding this comment.
Approving for following development.
Release Notes