Compare commits

...

4 Commits

Author SHA1 Message Date
luckyzen0312 61dff6834f
Merge f63e3a2258 into 82c7e631bb 2024-03-30 13:19:41 +05:45
priya-kinthali 82c7e631bb
Documentation changes for avoiding rate limit issues on GHES (#835)
* initial commit for documentation changes related to rawapi

* documentation changes and added check for validating raw api

* documenation changes for pr
2024-03-26 08:56:51 -05:00
Tobias 10aa35afd7
feat: fallback to raw endpoint for manifest when rate limit is reached (#766) 2024-03-26 08:56:00 -05:00
luckyzen0312 f63e3a2258
Create tea.yaml 2024-03-05 04:24:09 +08:00
6 changed files with 125 additions and 24 deletions

View File

@ -93,3 +93,7 @@ jobs:
python-version: '<3.11'
- name: Verify <3.11
run: python __tests__/verify-python.py 3.10
- name: Test Raw Endpoint Access
run: |
curl -L https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json | jq empty
shell: bash

View File

@ -0,0 +1,58 @@
import {
getManifest,
getManifestFromRepo,
getManifestFromURL
} from '../src/install-python';
import * as httpm from '@actions/http-client';
import * as tc from '@actions/tool-cache';
jest.mock('@actions/http-client');
jest.mock('@actions/tool-cache');
const mockManifest = [{version: '1.0.0'}];
describe('getManifest', () => {
it('should return manifest from repo', async () => {
(tc.getManifestFromRepo as jest.Mock).mockResolvedValue(mockManifest);
const manifest = await getManifest();
expect(manifest).toEqual(mockManifest);
});
it('should return manifest from URL if repo fetch fails', async () => {
(tc.getManifestFromRepo as jest.Mock).mockRejectedValue(
new Error('Fetch failed')
);
(httpm.HttpClient.prototype.getJson as jest.Mock).mockResolvedValue({
result: mockManifest
});
const manifest = await getManifest();
expect(manifest).toEqual(mockManifest);
});
});
describe('getManifestFromRepo', () => {
it('should return manifest from repo', async () => {
(tc.getManifestFromRepo as jest.Mock).mockResolvedValue(mockManifest);
const manifest = await getManifestFromRepo();
expect(manifest).toEqual(mockManifest);
});
});
describe('getManifestFromURL', () => {
it('should return manifest from URL', async () => {
(httpm.HttpClient.prototype.getJson as jest.Mock).mockResolvedValue({
result: mockManifest
});
const manifest = await getManifestFromURL();
expect(manifest).toEqual(mockManifest);
});
it('should throw error if unable to get manifest from URL', async () => {
(httpm.HttpClient.prototype.getJson as jest.Mock).mockResolvedValue({
result: null
});
await expect(getManifestFromURL()).rejects.toThrow(
'Unable to get manifest from'
);
});
});

32
dist/setup/index.js vendored
View File

@ -91388,11 +91388,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.installCpythonFromRelease = exports.getManifest = exports.findReleaseFromManifest = exports.MANIFEST_URL = void 0;
exports.installCpythonFromRelease = exports.getManifestFromURL = exports.getManifestFromRepo = exports.getManifest = exports.findReleaseFromManifest = exports.MANIFEST_URL = void 0;
const path = __importStar(__nccwpck_require__(1017));
const core = __importStar(__nccwpck_require__(2186));
const tc = __importStar(__nccwpck_require__(7784));
const exec = __importStar(__nccwpck_require__(1514));
const httpm = __importStar(__nccwpck_require__(6255));
const utils_1 = __nccwpck_require__(1314);
const TOKEN = core.getInput('token');
const AUTH = !TOKEN ? undefined : `token ${TOKEN}`;
@ -91411,10 +91412,37 @@ function findReleaseFromManifest(semanticVersionSpec, architecture, manifest) {
}
exports.findReleaseFromManifest = findReleaseFromManifest;
function getManifest() {
return __awaiter(this, void 0, void 0, function* () {
try {
return yield getManifestFromRepo();
}
catch (err) {
core.debug('Fetching the manifest via the API failed.');
if (err instanceof Error) {
core.debug(err.message);
}
}
return yield getManifestFromURL();
});
}
exports.getManifest = getManifest;
function getManifestFromRepo() {
core.debug(`Getting manifest from ${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}@${MANIFEST_REPO_BRANCH}`);
return tc.getManifestFromRepo(MANIFEST_REPO_OWNER, MANIFEST_REPO_NAME, AUTH, MANIFEST_REPO_BRANCH);
}
exports.getManifest = getManifest;
exports.getManifestFromRepo = getManifestFromRepo;
function getManifestFromURL() {
return __awaiter(this, void 0, void 0, function* () {
core.debug('Falling back to fetching the manifest using raw URL.');
const http = new httpm.HttpClient('tool-cache');
const response = yield http.getJson(exports.MANIFEST_URL);
if (!response.result) {
throw new Error(`Unable to get manifest from ${exports.MANIFEST_URL}`);
}
return response.result;
});
}
exports.getManifestFromURL = getManifestFromURL;
function installPython(workingDirectory) {
return __awaiter(this, void 0, void 0, function* () {
const options = {

View File

@ -572,29 +572,10 @@ One quick way to grant access is to change the user and group of `/Users/runner/
### Avoiding rate limit issues
`setup-python` comes pre-installed on the appliance with GHES if Actions is enabled. When dynamically downloading Python distributions, `setup-python` downloads distributions from [`actions/python-versions`](https://github.com/actions/python-versions) on github.com (outside of the appliance). These calls to `actions/python-versions` are by default made via unauthenticated requests, which are limited to [60 requests per hour per IP](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting). If more requests are made within the time frame, then you will start to see rate-limit errors during downloading that look like this:
##[error]API rate limit exceeded for YOUR_IP. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)
To get a higher rate limit, you can [generate a personal access token (PAT) on github.com](https://github.com/settings/tokens/new) and pass it as the `token` input for the action. It is important to understand that this needs to be a token from github.com and _not_ from your GHES instance. If you or your colleagues do not yet have a github.com account, you might need to create one.
Here are the steps you need to follow to avoid the rate limit:
1. Create a PAT on any github.com account by using [this link](https://github.com/settings/tokens/new) after logging into github.com (not your Enterprise instance). This PAT does _not_ need any rights, so make sure all the boxes are unchecked.
2. Store this PAT in the repository / organization where you run your workflow, e.g. as `GH_GITHUB_COM_TOKEN`. You can do this by navigating to your repository -> **Settings** -> **Secrets** -> **Actions** -> **New repository secret**.
3. To use this functionality, you need to use any version newer than `v4.3`. Also, change _python-version_ as needed.
```yml
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.8
token: ${{ secrets.GH_GITHUB_COM_TOKEN }}
```
Requests should now be authenticated. To verify that you are getting the higher rate limit, you can call GitHub's [rate limit API](https://docs.github.com/en/rest/rate-limit) from within your workflow ([example](https://github.com/actions/setup-python/pull/443#issuecomment-1206776401)).
`setup-python` comes pre-installed on the appliance with GHES if Actions is enabled. When dynamically downloading Python distributions, `setup-python` downloads distributions from [`actions/python-versions`](https://github.com/actions/python-versions) on github.com (outside of the appliance). These calls to `actions/python-versions` are by default made via unauthenticated requests, which are limited to [60 requests per hour per IP](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting). If more requests are made within the time frame, then the action leverages the `raw API` to retrieve the version-manifest. This approach does not impose a rate limit and hence facilitates unrestricted consumption. This is particularly beneficial for GHES runners, which often share the same IP due to Network Address Translation (NAT), to avoid the quick exhaustion of the unauthenticated rate limit.
### No access to github.com
If the runner is not able to access github.com, any Python versions requested during a workflow run must come from the runner's tool cache. See "[Setting up the tool cache on self-hosted runners without internet access](https://docs.github.com/en/enterprise-server/admin/github-actions/managing-access-to-actions-from-githubcom/setting-up-the-tool-cache-on-self-hosted-runners-without-internet-access)" for more information.

View File

@ -2,6 +2,7 @@ import * as path from 'path';
import * as core from '@actions/core';
import * as tc from '@actions/tool-cache';
import * as exec from '@actions/exec';
import * as httpm from '@actions/http-client';
import {ExecOptions} from '@actions/exec/lib/interfaces';
import {IS_WINDOWS, IS_LINUX} from './utils';
@ -31,7 +32,19 @@ export async function findReleaseFromManifest(
return foundRelease;
}
export function getManifest(): Promise<tc.IToolRelease[]> {
export async function getManifest(): Promise<tc.IToolRelease[]> {
try {
return await getManifestFromRepo();
} catch (err) {
core.debug('Fetching the manifest via the API failed.');
if (err instanceof Error) {
core.debug(err.message);
}
}
return await getManifestFromURL();
}
export function getManifestFromRepo(): Promise<tc.IToolRelease[]> {
core.debug(
`Getting manifest from ${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}@${MANIFEST_REPO_BRANCH}`
);
@ -43,6 +56,17 @@ export function getManifest(): Promise<tc.IToolRelease[]> {
);
}
export async function getManifestFromURL(): Promise<tc.IToolRelease[]> {
core.debug('Falling back to fetching the manifest using raw URL.');
const http: httpm.HttpClient = new httpm.HttpClient('tool-cache');
const response = await http.getJson<tc.IToolRelease[]>(MANIFEST_URL);
if (!response.result) {
throw new Error(`Unable to get manifest from ${MANIFEST_URL}`);
}
return response.result;
}
async function installPython(workingDirectory: string) {
const options: ExecOptions = {
cwd: workingDirectory,

6
tea.yaml Normal file
View File

@ -0,0 +1,6 @@
# https://tea.xyz/what-is-this-file
---
version: 1.0.0
codeOwners:
- '0x842390f1789e137a0E3012F0879eC6A92132D9a3'
quorum: 1