@@ -9,7 +9,6 @@ versions:
|
|||||||
children:
|
children:
|
||||||
- /viewing-github-actions-metrics
|
- /viewing-github-actions-metrics
|
||||||
- /making-retired-namespaces-available-on-ghecom
|
- /making-retired-namespaces-available-on-ghecom
|
||||||
- /managing-custom-actions
|
|
||||||
redirect_from:
|
redirect_from:
|
||||||
- /actions/administering-github-actions
|
- /actions/administering-github-actions
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
---
|
---
|
||||||
title: Developing a third party CLI action
|
title: Creating a third party CLI action
|
||||||
shortTitle: CLI setup action
|
shortTitle: Create a CLI action
|
||||||
intro: 'Learn how to develop an action to set up a CLI on {% data variables.product.prodname_actions %} runners.'
|
intro: 'Learn how to develop an action to set up a CLI on {% data variables.product.prodname_actions %} runners.'
|
||||||
redirect_from:
|
redirect_from:
|
||||||
- /actions/creating-actions/developing-a-third-party-cli-action
|
- /actions/creating-actions/developing-a-third-party-cli-action
|
||||||
- /actions/sharing-automations/creating-actions/developing-a-third-party-cli-action
|
- /actions/sharing-automations/creating-actions/developing-a-third-party-cli-action
|
||||||
|
- /actions/how-tos/sharing-automations/creating-actions/developing-a-third-party-cli-action
|
||||||
versions:
|
versions:
|
||||||
fpt: '*'
|
fpt: '*'
|
||||||
ghec: '*'
|
ghec: '*'
|
||||||
type: tutorial
|
|
||||||
topics:
|
topics:
|
||||||
- Actions
|
- Actions
|
||||||
---
|
---
|
||||||
@@ -29,7 +29,7 @@ This article will demonstrate how to write an action that retrieves a specific v
|
|||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
You should have an understanding of how to write a custom action. For more information, see [AUTOTITLE](/actions/creating-actions/about-custom-actions). For a more detailed guide on how to write a custom action, see [AUTOTITLE](/actions/creating-actions/creating-a-javascript-action).
|
You should have an understanding of how to write a custom action. For more information, see [AUTOTITLE](/actions/how-tos/creating-and-publishing-actions/managing-custom-actions).
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
---
|
---
|
||||||
title: Creating actions
|
title: Creating and publishing actions
|
||||||
shortTitle: Create actions
|
shortTitle: Create and publish actions
|
||||||
intro: 'You can create your own actions, use and customize actions shared by the {% data variables.product.prodname_dotcom %} community, or write and share the actions you build.'
|
intro: 'You can create your own actions, use and customize actions shared by the {% data variables.product.prodname_dotcom %} community, or write and share the actions you build.'
|
||||||
versions:
|
versions:
|
||||||
fpt: '*'
|
fpt: '*'
|
||||||
ghes: '*'
|
ghes: '*'
|
||||||
ghec: '*'
|
ghec: '*'
|
||||||
children:
|
|
||||||
- /setting-exit-codes-for-actions
|
|
||||||
- /releasing-and-maintaining-actions
|
|
||||||
- /publishing-actions-in-github-marketplace
|
|
||||||
- /developing-a-third-party-cli-action
|
|
||||||
redirect_from:
|
redirect_from:
|
||||||
- /actions/sharing-automations/creating-actions
|
- /actions/sharing-automations/creating-actions
|
||||||
|
- /actions/how-tos/sharing-automations/creating-actions
|
||||||
|
children:
|
||||||
|
- /managing-custom-actions
|
||||||
|
- /creating-a-third-party-cli-action
|
||||||
|
- /setting-exit-codes-for-actions
|
||||||
|
- /publishing-actions-in-github-marketplace
|
||||||
|
- /releasing-and-maintaining-actions
|
||||||
---
|
---
|
||||||
|
|
||||||
{% data reusables.actions.enterprise-github-hosted-runners %}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: Managing custom actions
|
title: Managing custom actions
|
||||||
|
shortTitle: Manage custom actions
|
||||||
intro: 'Learn how to create and manage your own actions, and customize actions shared by the {% data variables.product.prodname_dotcom %} community.'
|
intro: 'Learn how to create and manage your own actions, and customize actions shared by the {% data variables.product.prodname_dotcom %} community.'
|
||||||
versions:
|
versions:
|
||||||
fpt: '*'
|
fpt: '*'
|
||||||
@@ -9,6 +10,8 @@ type: overview
|
|||||||
topics:
|
topics:
|
||||||
- Action development
|
- Action development
|
||||||
- Fundamentals
|
- Fundamentals
|
||||||
|
redirect_from:
|
||||||
|
- /actions/how-tos/administering-github-actions/managing-custom-actions
|
||||||
---
|
---
|
||||||
|
|
||||||
## Choosing a location for your action
|
## Choosing a location for your action
|
||||||
@@ -7,16 +7,17 @@ redirect_from:
|
|||||||
- /actions/building-actions/publishing-actions-in-github-marketplace
|
- /actions/building-actions/publishing-actions-in-github-marketplace
|
||||||
- /actions/creating-actions/publishing-actions-in-github-marketplace
|
- /actions/creating-actions/publishing-actions-in-github-marketplace
|
||||||
- /actions/sharing-automations/creating-actions/publishing-actions-in-github-marketplace
|
- /actions/sharing-automations/creating-actions/publishing-actions-in-github-marketplace
|
||||||
|
- /actions/how-tos/sharing-automations/creating-actions/publishing-actions-in-github-marketplace
|
||||||
versions:
|
versions:
|
||||||
fpt: '*'
|
fpt: '*'
|
||||||
ghec: '*'
|
ghec: '*'
|
||||||
type: how_to
|
|
||||||
shortTitle: Publish in GitHub Marketplace
|
shortTitle: Publish in GitHub Marketplace
|
||||||
---
|
---
|
||||||
|
|
||||||
You must accept the terms of service to publish actions in {% data variables.product.prodname_marketplace %}.
|
## Prerequisites
|
||||||
|
|
||||||
## About publishing actions
|
>[!NOTE]
|
||||||
|
> You must accept the terms of service to publish actions in {% data variables.product.prodname_marketplace %}.
|
||||||
|
|
||||||
Before you can publish an action, you'll need to create an action in your repository. For more information, see [AUTOTITLE](/actions/creating-actions).
|
Before you can publish an action, you'll need to create an action in your repository. For more information, see [AUTOTITLE](/actions/creating-actions).
|
||||||
|
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
title: Releasing and maintaining actions
|
title: Releasing and maintaining actions
|
||||||
shortTitle: Release and maintain actions
|
shortTitle: Release and maintain actions
|
||||||
intro: You can leverage automation and open source best practices to release and maintain actions.
|
intro: You can leverage automation and open source best practices to release and maintain actions.
|
||||||
type: tutorial
|
|
||||||
topics:
|
topics:
|
||||||
- Action development
|
- Action development
|
||||||
- Actions
|
- Actions
|
||||||
@@ -14,6 +13,7 @@ versions:
|
|||||||
redirect_from:
|
redirect_from:
|
||||||
- /actions/creating-actions/releasing-and-maintaining-actions
|
- /actions/creating-actions/releasing-and-maintaining-actions
|
||||||
- /actions/sharing-automations/creating-actions/releasing-and-maintaining-actions
|
- /actions/sharing-automations/creating-actions/releasing-and-maintaining-actions
|
||||||
|
- /actions/how-tos/sharing-automations/creating-actions/releasing-and-maintaining-actions
|
||||||
---
|
---
|
||||||
|
|
||||||
{% data reusables.actions.enterprise-github-hosted-runners %}
|
{% data reusables.actions.enterprise-github-hosted-runners %}
|
||||||
@@ -6,11 +6,11 @@ redirect_from:
|
|||||||
- /actions/building-actions/setting-exit-codes-for-actions
|
- /actions/building-actions/setting-exit-codes-for-actions
|
||||||
- /actions/creating-actions/setting-exit-codes-for-actions
|
- /actions/creating-actions/setting-exit-codes-for-actions
|
||||||
- /actions/sharing-automations/creating-actions/setting-exit-codes-for-actions
|
- /actions/sharing-automations/creating-actions/setting-exit-codes-for-actions
|
||||||
|
- /actions/how-tos/sharing-automations/creating-actions/setting-exit-codes-for-actions
|
||||||
versions:
|
versions:
|
||||||
fpt: '*'
|
fpt: '*'
|
||||||
ghes: '*'
|
ghes: '*'
|
||||||
ghec: '*'
|
ghec: '*'
|
||||||
type: how_to
|
|
||||||
---
|
---
|
||||||
|
|
||||||
{% data reusables.actions.enterprise-github-hosted-runners %}
|
{% data reusables.actions.enterprise-github-hosted-runners %}
|
||||||
@@ -9,6 +9,7 @@ versions:
|
|||||||
children:
|
children:
|
||||||
- /writing-workflows
|
- /writing-workflows
|
||||||
- /managing-workflow-runs-and-deployments
|
- /managing-workflow-runs-and-deployments
|
||||||
|
- /creating-and-publishing-actions
|
||||||
- /sharing-automations
|
- /sharing-automations
|
||||||
- /using-github-hosted-runners
|
- /using-github-hosted-runners
|
||||||
- /managing-self-hosted-runners
|
- /managing-self-hosted-runners
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ redirect_from:
|
|||||||
- /articles/creating-a-github-action
|
- /articles/creating-a-github-action
|
||||||
- /actions/sharing-automations
|
- /actions/sharing-automations
|
||||||
children:
|
children:
|
||||||
- /creating-actions
|
|
||||||
- /reuse-workflows
|
- /reuse-workflows
|
||||||
- /creating-workflow-templates-for-your-organization
|
- /creating-workflow-templates-for-your-organization
|
||||||
- /sharing-actions-and-workflows-from-your-private-repository
|
- /sharing-actions-and-workflows-from-your-private-repository
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ You must authorize your {% data variables.product.pat_v1 %} after creation befor
|
|||||||
|
|
||||||
1. In the dropdown menu, to the right of the organization you'd like to authorize the token for, click **Authorize**.
|
1. In the dropdown menu, to the right of the organization you'd like to authorize the token for, click **Authorize**.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> When authorizing a {% data variables.product.pat_v1 %} for use within an organization that belongs to an enterprise which has both an IP allow list and single sign-on enabled at the enterprise level, your IP must also be allowed at the enterprise level. See [AUTOTITLE](/admin/configuring-settings/hardening-security-for-your-enterprise/restricting-network-traffic-to-your-enterprise-with-an-ip-allow-list).
|
||||||
|
|
||||||
## Further reading
|
## Further reading
|
||||||
|
|
||||||
* [AUTOTITLE](/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token)
|
* [AUTOTITLE](/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token)
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ You do not need to authorize SSH certificates signed by your organization's SSH
|
|||||||

|

|
||||||
1. In the dropdown menu, to the right of the organization you'd like to authorize the SSH key for, click **Authorize**.
|
1. In the dropdown menu, to the right of the organization you'd like to authorize the SSH key for, click **Authorize**.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> When authorizing an SSH key for use within an organization that belongs to an enterprise which has both an IP allow list and single sign-on enabled at the enterprise level, your IP must also be allowed at the enterprise level. See [AUTOTITLE](/admin/configuring-settings/hardening-security-for-your-enterprise/restricting-network-traffic-to-your-enterprise-with-an-ip-allow-list).
|
||||||
|
|
||||||
## Further reading
|
## Further reading
|
||||||
|
|
||||||
* [AUTOTITLE](/authentication/connecting-to-github-with-ssh/checking-for-existing-ssh-keys)
|
* [AUTOTITLE](/authentication/connecting-to-github-with-ssh/checking-for-existing-ssh-keys)
|
||||||
|
|||||||
@@ -33,7 +33,13 @@ For specific ecosystems, you can configure {% data variables.product.prodname_de
|
|||||||
|
|
||||||
## Configuring private registries
|
## Configuring private registries
|
||||||
|
|
||||||
You configure {% data variables.product.prodname_dependabot %}'s access to private registries in the `dependabot.yml` file.
|
{% ifversion org-private-registry %}
|
||||||
|
|
||||||
|
You can configure {% data variables.product.prodname_dependabot %}'s access to private registries at the org-level. For more information on how to configure that, see [AUTOTITLE](/code-security/securing-your-organization/enabling-security-features-in-your-organization/giving-org-access-private-registries).
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
You can also configure {% data variables.product.prodname_dependabot %}'s access to private registries in the `dependabot.yml` file.
|
||||||
The top-level `registries` key is optional and specifies authentication details.
|
The top-level `registries` key is optional and specifies authentication details.
|
||||||
|
|
||||||
{% data reusables.dependabot.dependabot-updates-registries %}
|
{% data reusables.dependabot.dependabot-updates-registries %}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ Any private registries used by the build must also be accessible to the workflow
|
|||||||
|
|
||||||
## {% data variables.product.prodname_dependabot %} updates access to private registries
|
## {% data variables.product.prodname_dependabot %} updates access to private registries
|
||||||
|
|
||||||
{% data variables.product.prodname_dependabot %} uses any private registries defined in the `dependabot.yml` file. It does not have access to the organization-level private registries used by {% data variables.product.prodname_code_scanning %} default setup.
|
{% data variables.product.prodname_dependabot %} can use any of the org-level private registries, as well as uses any private registries defined in the `dependabot.yml` file in the repo.
|
||||||
|
|
||||||
{% data variables.product.prodname_dependabot %} cannot check for security or version updates for code stored in a private registry unless it can access the registry. If you do not configure access to the private registry, then {% data variables.product.prodname_dependabot %} cannot raise pull requests to update any of the dependencies stored in the registry.
|
{% data variables.product.prodname_dependabot %} cannot check for security or version updates for code stored in a private registry unless it can access the registry. If you do not configure access to the private registry, then {% data variables.product.prodname_dependabot %} cannot raise pull requests to update any of the dependencies stored in the registry.
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ Content exclusions also apply to {% data variables.copilot.copilot_code-review_s
|
|||||||
|
|
||||||
{% data reusables.copilot.content-exclusion-limitations %}
|
{% data reusables.copilot.content-exclusion-limitations %}
|
||||||
|
|
||||||
Currently, content exclusions do not apply to symbolic links (symlinks).
|
Currently, content exclusions do not apply to symbolic links (symlinks) and repositories located on remote filesystems.
|
||||||
|
|
||||||
### Data sent to {% data variables.product.prodname_dotcom %}
|
### Data sent to {% data variables.product.prodname_dotcom %}
|
||||||
|
|
||||||
|
|||||||
@@ -49,4 +49,6 @@ Used for:
|
|||||||
|
|
||||||
{% data variables.product.prodname_copilot %} uses {% data variables.copilot.copilot_gemini_flash %} and {% data variables.copilot.copilot_gemini_25_pro %} hosted on Google Cloud Platform (GCP). When using {% data variables.copilot.copilot_gemini %} models, prompts and metadata are sent to GCP, which makes the [following data commitment](https://cloud.google.com/vertex-ai/generative-ai/docs/data-governance): _{% data variables.copilot.copilot_gemini %} doesn't use your prompts, or its responses, as data to train its models._
|
{% data variables.product.prodname_copilot %} uses {% data variables.copilot.copilot_gemini_flash %} and {% data variables.copilot.copilot_gemini_25_pro %} hosted on Google Cloud Platform (GCP). When using {% data variables.copilot.copilot_gemini %} models, prompts and metadata are sent to GCP, which makes the [following data commitment](https://cloud.google.com/vertex-ai/generative-ai/docs/data-governance): _{% data variables.copilot.copilot_gemini %} doesn't use your prompts, or its responses, as data to train its models._
|
||||||
|
|
||||||
|
To provide better service quality and reduce latency, {% data variables.product.github %} uses [prompt caching](https://cloud.google.com/vertex-ai/generative-ai/docs/data-governance#customer_data_retention_and_achieving_zero_data_retention).
|
||||||
|
|
||||||
When using {% data variables.copilot.copilot_gemini %} models, input prompts and output completions continue to run through {% data variables.product.prodname_copilot %}'s content filters for public code matching, when applied, along with those for harmful or offensive content.
|
When using {% data variables.copilot.copilot_gemini %} models, input prompts and output completions continue to run through {% data variables.product.prodname_copilot %}'s content filters for public code matching, when applied, along with those for harmful or offensive content.
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
>[!NOTE] Repository policies are currently in {% data variables.release-phases.public_preview %} and subject to change.
|
>[!NOTE] Repository policies are currently in {% data variables.release-phases.public_preview %} and subject to change.
|
||||||
|
>You can have up to 75 total policies and rulesets per organization, and up to 75 total policies and rulesets per enterprise.
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
> [!NOTE]
|
> {% ifversion ghes > 3.16 %}[!NOTE]
|
||||||
> {% ifversion ghes > 3.16 %}Only changes made to a ruleset after you have upgraded to {% data variables.product.prodname_ghe_server %} 3.17.0, or a later version, are included in the ruleset history.{% endif %}
|
> Only changes made to a ruleset after you have upgraded to {% data variables.product.prodname_ghe_server %} 3.17.0, or a later version, are included in the ruleset history.{% endif %}
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ If your support request is outside of the scope of what our team can help you wi
|
|||||||
|
|
||||||
* Third party integrations, such as Jira{% ifversion ghes %}
|
* Third party integrations, such as Jira{% ifversion ghes %}
|
||||||
* Hardware setup
|
* Hardware setup
|
||||||
* Hypervisor-related issues, such as OS disk detection, network setup, boot failure, VM console access, etc{% endif %}
|
* Hypervisor-related issues, such as OS disk detection, network setup, boot failure, VM console access, etc.
|
||||||
|
* Migration assistance between specific hardware platforms or storage providers{% endif %}
|
||||||
* CI/CD, such as Jenkins
|
* CI/CD, such as Jenkins
|
||||||
* Azure DevOps (please contact Azure Support)
|
* Azure DevOps (please contact Azure Support)
|
||||||
* Writing scripts
|
* Writing scripts
|
||||||
|
|||||||
42
package-lock.json
generated
42
package-lock.json
generated
@@ -4783,19 +4783,44 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/request/node_modules/form-data": {
|
"node_modules/@types/request/node_modules/form-data": {
|
||||||
"version": "2.5.1",
|
"version": "2.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz",
|
||||||
"integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==",
|
"integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"asynckit": "^0.4.0",
|
"asynckit": "^0.4.0",
|
||||||
"combined-stream": "^1.0.6",
|
"combined-stream": "^1.0.8",
|
||||||
"mime-types": "^2.1.12"
|
"es-set-tostringtag": "^2.1.0",
|
||||||
|
"hasown": "^2.0.2",
|
||||||
|
"mime-types": "^2.1.35",
|
||||||
|
"safe-buffer": "^5.2.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.12"
|
"node": ">= 0.12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/request/node_modules/safe-buffer": {
|
||||||
|
"version": "5.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||||
|
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/semver": {
|
"node_modules/@types/semver": {
|
||||||
"version": "7.5.8",
|
"version": "7.5.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
|
||||||
@@ -8957,14 +8982,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/form-data": {
|
"node_modules/form-data": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||||
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
|
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"asynckit": "^0.4.0",
|
"asynckit": "^0.4.0",
|
||||||
"combined-stream": "^1.0.8",
|
"combined-stream": "^1.0.8",
|
||||||
"es-set-tostringtag": "^2.1.0",
|
"es-set-tostringtag": "^2.1.0",
|
||||||
|
"hasown": "^2.0.2",
|
||||||
"mime-types": "^2.1.12"
|
"mime-types": "^2.1.12"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -103,11 +103,25 @@ export async function syncGitHubAppsData(openApiSource, sourceSchemas, progAcces
|
|||||||
|
|
||||||
const excludedActors = progActorResources[permissionName]['excluded_actors']
|
const excludedActors = progActorResources[permissionName]['excluded_actors']
|
||||||
|
|
||||||
const additionalPermissions =
|
const additionalPermissions = calculateAdditionalPermissions(
|
||||||
progAccessData[operation.operationId].permissions.length > 1 ||
|
progAccessData[operation.operationId].permissions,
|
||||||
progAccessData[operation.operationId].permissions.some(
|
)
|
||||||
(permissionSet) => Object.keys(permissionSet).length > 1,
|
|
||||||
|
// Filter out metadata permissions when combined with other permissions
|
||||||
|
// The metadata permission is automatically granted with any other repository permission,
|
||||||
|
// so documenting it for operations that require additional permissions is misleading.
|
||||||
|
// This fixes the issue where mutating operations (PUT, DELETE) incorrectly appeared
|
||||||
|
// to only need metadata access when they actually require write permissions.
|
||||||
|
// See: https://github.com/github/docs-engineering/issues/5212
|
||||||
|
if (
|
||||||
|
shouldFilterMetadataPermission(
|
||||||
|
permissionName,
|
||||||
|
progAccessData[operation.operationId].permissions,
|
||||||
)
|
)
|
||||||
|
) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// github app permissions
|
// github app permissions
|
||||||
if (!isActorExcluded(excludedActors, 'server_to_server', actorTypeMap)) {
|
if (!isActorExcluded(excludedActors, 'server_to_server', actorTypeMap)) {
|
||||||
const serverToServerPermissions = githubAppsData['server-to-server-permissions']
|
const serverToServerPermissions = githubAppsData['server-to-server-permissions']
|
||||||
@@ -332,6 +346,28 @@ function sentenceCase(str) {
|
|||||||
return str.charAt(0).toUpperCase() + str.slice(1)
|
return str.charAt(0).toUpperCase() + str.slice(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates whether an operation has additional permissions beyond a single permission.
|
||||||
|
*/
|
||||||
|
export function calculateAdditionalPermissions(permissionSets) {
|
||||||
|
return (
|
||||||
|
permissionSets.length > 1 ||
|
||||||
|
permissionSets.some((permissionSet) => Object.keys(permissionSet).length > 1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether a metadata permission should be filtered out when it has additional permissions.
|
||||||
|
* Prevents misleading documentation where mutating operations appear to only need metadata access.
|
||||||
|
*/
|
||||||
|
export function shouldFilterMetadataPermission(permissionName, permissionSets) {
|
||||||
|
if (permissionName !== 'metadata') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return calculateAdditionalPermissions(permissionSets)
|
||||||
|
}
|
||||||
|
|
||||||
export function isActorExcluded(excludedActors, actorType, actorTypeMap = {}) {
|
export function isActorExcluded(excludedActors, actorType, actorTypeMap = {}) {
|
||||||
if (!excludedActors || !Array.isArray(excludedActors)) {
|
if (!excludedActors || !Array.isArray(excludedActors)) {
|
||||||
return false
|
return false
|
||||||
@@ -358,7 +394,6 @@ export function isActorExcluded(excludedActors, actorType, actorTypeMap = {}) {
|
|||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
function addAppData(storage, category, data) {
|
function addAppData(storage, category, data) {
|
||||||
if (!storage[category]) {
|
if (!storage[category]) {
|
||||||
storage[category] = []
|
storage[category] = []
|
||||||
|
|||||||
308
src/github-apps/tests/metadata-permissions.js
Normal file
308
src/github-apps/tests/metadata-permissions.js
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
import { describe, expect, test } from 'vitest'
|
||||||
|
import { shouldFilterMetadataPermission, calculateAdditionalPermissions } from '../scripts/sync.js'
|
||||||
|
|
||||||
|
describe('metadata permissions filtering', () => {
|
||||||
|
// Mock data structure representing operations with metadata permissions
|
||||||
|
const mockOperationsWithMetadata = [
|
||||||
|
{
|
||||||
|
operationId: 'repos/enable-automated-security-fixes',
|
||||||
|
permissionSets: [{ metadata: 'read', administration: 'write' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
operationId: 'repos/get-readme',
|
||||||
|
permissionSets: [{ metadata: 'read' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
operationId: 'orgs/update-webhook',
|
||||||
|
permissionSets: [{ metadata: 'read', organization_administration: 'write' }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
operationId: 'repos/get-content',
|
||||||
|
permissionSets: [{ contents: 'read' }],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
// Mock programmatic access data
|
||||||
|
const mockProgAccessData = {
|
||||||
|
'repos/enable-automated-security-fixes': {
|
||||||
|
userToServerRest: true,
|
||||||
|
serverToServer: true,
|
||||||
|
permissions: [{ metadata: 'read', administration: 'write' }],
|
||||||
|
},
|
||||||
|
'repos/get-readme': {
|
||||||
|
userToServerRest: true,
|
||||||
|
serverToServer: true,
|
||||||
|
permissions: [{ metadata: 'read' }],
|
||||||
|
},
|
||||||
|
'orgs/update-webhook': {
|
||||||
|
userToServerRest: true,
|
||||||
|
serverToServer: true,
|
||||||
|
permissions: [{ metadata: 'read', organization_administration: 'write' }],
|
||||||
|
},
|
||||||
|
'repos/get-content': {
|
||||||
|
userToServerRest: true,
|
||||||
|
serverToServer: true,
|
||||||
|
permissions: [{ contents: 'read' }],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock actor resources
|
||||||
|
const mockProgActorResources = {
|
||||||
|
metadata: {
|
||||||
|
title: 'Metadata',
|
||||||
|
visibility: 'public',
|
||||||
|
},
|
||||||
|
administration: {
|
||||||
|
title: 'Administration',
|
||||||
|
visibility: 'public',
|
||||||
|
},
|
||||||
|
organization_administration: {
|
||||||
|
title: 'Organization administration',
|
||||||
|
visibility: 'public',
|
||||||
|
},
|
||||||
|
contents: {
|
||||||
|
title: 'Contents',
|
||||||
|
visibility: 'public',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
test('calculateAdditionalPermissions works correctly', () => {
|
||||||
|
// Single permission set with multiple permissions
|
||||||
|
expect(calculateAdditionalPermissions([{ metadata: 'read', admin: 'write' }])).toBe(true)
|
||||||
|
|
||||||
|
// Single permission set with single permission
|
||||||
|
expect(calculateAdditionalPermissions([{ metadata: 'read' }])).toBe(false)
|
||||||
|
|
||||||
|
// Multiple permission sets
|
||||||
|
expect(calculateAdditionalPermissions([{ metadata: 'read' }, { admin: 'write' }])).toBe(true)
|
||||||
|
|
||||||
|
// Empty permission sets
|
||||||
|
expect(calculateAdditionalPermissions([])).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('identifies metadata with additional permissions correctly', () => {
|
||||||
|
// Case 1: metadata + administration (should be filtered)
|
||||||
|
const metadataWithAdmin = [{ metadata: 'read', administration: 'write' }]
|
||||||
|
expect(shouldFilterMetadataPermission('metadata', metadataWithAdmin)).toBe(true)
|
||||||
|
|
||||||
|
// Case 2: metadata only (should NOT be filtered)
|
||||||
|
const metadataOnly = [{ metadata: 'read' }]
|
||||||
|
expect(shouldFilterMetadataPermission('metadata', metadataOnly)).toBe(false)
|
||||||
|
|
||||||
|
// Case 3: non-metadata permission (should NOT be filtered)
|
||||||
|
const nonMetadata = [{ contents: 'read' }]
|
||||||
|
expect(shouldFilterMetadataPermission('contents', nonMetadata)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('filters metadata operations with additional permissions', () => {
|
||||||
|
const filteredOperations = []
|
||||||
|
const metadataPermissions = []
|
||||||
|
|
||||||
|
for (const operation of mockOperationsWithMetadata) {
|
||||||
|
const progData = mockProgAccessData[operation.operationId]
|
||||||
|
|
||||||
|
for (const permissionSet of progData.permissions) {
|
||||||
|
for (const [permissionName] of Object.entries(permissionSet)) {
|
||||||
|
if (mockProgActorResources[permissionName]?.visibility === 'private') continue
|
||||||
|
|
||||||
|
const additionalPermissions = calculateAdditionalPermissions(progData.permissions)
|
||||||
|
|
||||||
|
// Apply metadata filtering logic
|
||||||
|
if (shouldFilterMetadataPermission(permissionName, progData.permissions)) {
|
||||||
|
// Skip this metadata permission as it has additional permissions
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (permissionName === 'metadata') {
|
||||||
|
metadataPermissions.push({
|
||||||
|
operationId: operation.operationId,
|
||||||
|
additionalPermissions,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
filteredOperations.push({
|
||||||
|
operationId: operation.operationId,
|
||||||
|
permission: permissionName,
|
||||||
|
additionalPermissions,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should only have metadata permission from the metadata-only operation
|
||||||
|
expect(metadataPermissions).toHaveLength(1)
|
||||||
|
expect(metadataPermissions[0].operationId).toBe('repos/get-readme')
|
||||||
|
expect(metadataPermissions[0].additionalPermissions).toBe(false)
|
||||||
|
|
||||||
|
// Should have other permissions from operations with additional permissions
|
||||||
|
const adminPermission = filteredOperations.find((op) => op.permission === 'administration')
|
||||||
|
expect(adminPermission).toBeDefined()
|
||||||
|
expect(adminPermission.operationId).toBe('repos/enable-automated-security-fixes')
|
||||||
|
expect(adminPermission.additionalPermissions).toBe(true)
|
||||||
|
|
||||||
|
const orgAdminPermission = filteredOperations.find(
|
||||||
|
(op) => op.permission === 'organization_administration',
|
||||||
|
)
|
||||||
|
expect(orgAdminPermission).toBeDefined()
|
||||||
|
expect(orgAdminPermission.operationId).toBe('orgs/update-webhook')
|
||||||
|
expect(orgAdminPermission.additionalPermissions).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('preserves non-metadata permissions regardless of additional permissions', () => {
|
||||||
|
const nonMetadataOperations = mockOperationsWithMetadata.filter(
|
||||||
|
(op) =>
|
||||||
|
!mockProgAccessData[op.operationId].permissions.some((permSet) => 'metadata' in permSet),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(nonMetadataOperations).toHaveLength(1)
|
||||||
|
expect(nonMetadataOperations[0].operationId).toBe('repos/get-content')
|
||||||
|
|
||||||
|
// Verify contents permission would be preserved
|
||||||
|
const contentsPermissionSet = mockProgAccessData['repos/get-content'].permissions[0]
|
||||||
|
expect('contents' in contentsPermissionSet).toBe(true)
|
||||||
|
expect('metadata' in contentsPermissionSet).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('handles edge cases in permission sets', () => {
|
||||||
|
// Empty permission set
|
||||||
|
expect(shouldFilterMetadataPermission('metadata', [])).toBe(false)
|
||||||
|
|
||||||
|
// Permission set with empty object (edge case)
|
||||||
|
const edgeCase1 = [{ metadata: 'read' }, {}]
|
||||||
|
expect(shouldFilterMetadataPermission('metadata', edgeCase1)).toBe(true)
|
||||||
|
|
||||||
|
// Multiple permission sets with metadata in different sets
|
||||||
|
const edgeCase2 = [{ metadata: 'read' }, { admin: 'write' }]
|
||||||
|
expect(shouldFilterMetadataPermission('metadata', edgeCase2)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('filters metadata permissions that match the GitHub issue examples', () => {
|
||||||
|
// These are examples from the GitHub issue that should be filtered out
|
||||||
|
// PUT /orgs/{org}/actions/permissions/repositories/{repository_id}
|
||||||
|
const putActionsPermissions = [{ metadata: 'read', organization_administration: 'write' }]
|
||||||
|
|
||||||
|
// DELETE /orgs/{org}/actions/permissions/repositories/{repository_id}
|
||||||
|
const deleteActionsPermissions = [{ metadata: 'read', organization_administration: 'write' }]
|
||||||
|
|
||||||
|
// These should be filtered out because they have metadata + additional permissions
|
||||||
|
expect(shouldFilterMetadataPermission('metadata', putActionsPermissions)).toBe(true)
|
||||||
|
expect(shouldFilterMetadataPermission('metadata', deleteActionsPermissions)).toBe(true)
|
||||||
|
|
||||||
|
// But the organization_administration permissions should NOT be filtered
|
||||||
|
expect(
|
||||||
|
shouldFilterMetadataPermission('organization_administration', putActionsPermissions),
|
||||||
|
).toBe(false)
|
||||||
|
expect(
|
||||||
|
shouldFilterMetadataPermission('organization_administration', deleteActionsPermissions),
|
||||||
|
).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('preserves metadata permissions that are standalone', () => {
|
||||||
|
// Example of a metadata-only permission that should be preserved
|
||||||
|
const metadataOnlyPermissions = [{ metadata: 'read' }]
|
||||||
|
|
||||||
|
// This should NOT be filtered out
|
||||||
|
expect(shouldFilterMetadataPermission('metadata', metadataOnlyPermissions)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('handles complex permission structures from real data', () => {
|
||||||
|
// Multiple permission sets (should filter metadata)
|
||||||
|
const multiplePermissionSets = [{ metadata: 'read' }, { administration: 'write' }]
|
||||||
|
expect(shouldFilterMetadataPermission('metadata', multiplePermissionSets)).toBe(true)
|
||||||
|
|
||||||
|
// Single permission set with multiple permissions (should filter metadata)
|
||||||
|
const multiplePermissionsInSet = [
|
||||||
|
{ metadata: 'read', contents: 'write', pull_requests: 'write' },
|
||||||
|
]
|
||||||
|
expect(shouldFilterMetadataPermission('metadata', multiplePermissionsInSet)).toBe(true)
|
||||||
|
|
||||||
|
// Multiple permission sets where metadata is not in the first set
|
||||||
|
const metadataInSecondSet = [{ administration: 'write' }, { metadata: 'read' }]
|
||||||
|
expect(shouldFilterMetadataPermission('metadata', metadataInSecondSet)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('validates filtering logic against known problematic endpoints', () => {
|
||||||
|
// Based on the issue description, these types of operations should have
|
||||||
|
// their metadata permissions filtered out:
|
||||||
|
|
||||||
|
// Runner group operations
|
||||||
|
const runnerGroupPermissions = [{ metadata: 'read', organization_administration: 'write' }]
|
||||||
|
|
||||||
|
// Organization secrets operations
|
||||||
|
const orgSecretsPermissions = [{ metadata: 'read', organization_secrets: 'write' }]
|
||||||
|
|
||||||
|
// Repository operations with admin permissions
|
||||||
|
const repoAdminPermissions = [{ metadata: 'read', administration: 'write' }]
|
||||||
|
|
||||||
|
// All of these should filter out metadata
|
||||||
|
expect(shouldFilterMetadataPermission('metadata', runnerGroupPermissions)).toBe(true)
|
||||||
|
expect(shouldFilterMetadataPermission('metadata', orgSecretsPermissions)).toBe(true)
|
||||||
|
expect(shouldFilterMetadataPermission('metadata', repoAdminPermissions)).toBe(true)
|
||||||
|
|
||||||
|
// But should preserve the actual required permissions
|
||||||
|
expect(
|
||||||
|
shouldFilterMetadataPermission('organization_administration', runnerGroupPermissions),
|
||||||
|
).toBe(false)
|
||||||
|
expect(shouldFilterMetadataPermission('organization_secrets', orgSecretsPermissions)).toBe(
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
expect(shouldFilterMetadataPermission('administration', repoAdminPermissions)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('verifies consistency with additional-permissions flag calculation', () => {
|
||||||
|
const testCases = [
|
||||||
|
// Single permission, single set - no additional permissions
|
||||||
|
{ permissionSets: [{ metadata: 'read' }], expected: false },
|
||||||
|
|
||||||
|
// Multiple permissions, single set - has additional permissions
|
||||||
|
{ permissionSets: [{ metadata: 'read', admin: 'write' }], expected: true },
|
||||||
|
|
||||||
|
// Single permission, multiple sets - has additional permissions
|
||||||
|
{ permissionSets: [{ metadata: 'read' }, { admin: 'write' }], expected: true },
|
||||||
|
|
||||||
|
// Multiple permissions, multiple sets - has additional permissions
|
||||||
|
{
|
||||||
|
permissionSets: [{ metadata: 'read', contents: 'read' }, { admin: 'write' }],
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const testCase of testCases) {
|
||||||
|
const additionalPermissions = calculateAdditionalPermissions(testCase.permissionSets)
|
||||||
|
const shouldFilter = shouldFilterMetadataPermission('metadata', testCase.permissionSets)
|
||||||
|
|
||||||
|
// The filtering logic should match the additional permissions calculation
|
||||||
|
expect(shouldFilter).toBe(additionalPermissions)
|
||||||
|
expect(additionalPermissions).toBe(testCase.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('validates filtering logic matches expected behavior from issue', () => {
|
||||||
|
// Based on the GitHub issue, these operations should be filtered out from metadata:
|
||||||
|
// - PUT /orgs/{org}/actions/permissions/repositories/{repository_id}
|
||||||
|
// - DELETE /orgs/{org}/actions/permissions/repositories/{repository_id}
|
||||||
|
// Because they have metadata + organization_administration permissions
|
||||||
|
|
||||||
|
const mockMutatingOperation = {
|
||||||
|
operationId: 'actions/set-selected-repositories-enabled-github-actions-organization',
|
||||||
|
permissionSets: [{ metadata: 'read', organization_administration: 'write' }],
|
||||||
|
}
|
||||||
|
|
||||||
|
const progData = {
|
||||||
|
userToServerRest: true,
|
||||||
|
serverToServer: true,
|
||||||
|
permissions: [{ metadata: 'read', organization_administration: 'write' }],
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should be filtered out from metadata permissions
|
||||||
|
expect(shouldFilterMetadataPermission('metadata', progData.permissions)).toBe(true)
|
||||||
|
|
||||||
|
// But organization_administration permission should still be included
|
||||||
|
expect(
|
||||||
|
shouldFilterMetadataPermission('organization_administration', progData.permissions),
|
||||||
|
).toBe(false)
|
||||||
|
|
||||||
|
// Verify additional permissions flag is set correctly
|
||||||
|
expect(calculateAdditionalPermissions(progData.permissions)).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user