Skip to content
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

Add support for GHE #618

Merged
merged 1 commit into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ Full list of the options

| NAME | DESCRIPTION | TYPE | DEFAULT | OPTIONS |
| ----------------------------------- | -------------------------------------------------------------- | -------- | --------------------- | ---------------------------------------- |
| `github-api-url` | The Github API endpoint. Override for Github Enterprise usage. | `string` | `true` | `https://api.github.com` |
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least for REST endpoint, this option name is good 👍 ref: actions/create-github-app-token#88


Suggested change
| `github-api-url` | The Github API endpoint. Override for Github Enterprise usage. | `string` | `true` | `https://api.github.com` |
| `github-api-url` | The Github API endpoint. Override for Github Enterprise usage. | `string` | `${{ github.api_url }}` | `https://api.github.com` |

? (sorry for unformatting)

| `github-token` | The GITHUB_TOKEN secret. You can use PAT if you want. | `string` | `${{ github.token }}` | |
| `wait-seconds-before-first-polling` | Wait this interval before first polling | `number` | `10` | |
| `min-interval-seconds` | Wait this interval or the multiplied value (and jitter) | `number` | `15` | |
Expand All @@ -90,6 +91,15 @@ permissions:
actions: read
```

## Support for Github Enterprise

To run this action in your Github Enterprise (GHE) instance you need to override `github-api-url`:

```yaml
with:
github-api-url: 'https://ghe-host.acme.net/api/v3'
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
github-api-url: 'https://ghe-host.acme.net/api/v3'
github-api-url: 'https://ghe-host.example.net/api/v3'

I don't know the detail for acme and the domain owner, don't we need to use example.* for this use? https://www.rfc-editor.org/rfc/rfc2606.txt

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I guess resolving conflict drops old review points... #618 (comment)

Sorry for my lately response 🙇‍♂️

```

## outputs.<output_id>

- `dump`\
Expand Down
2 changes: 2 additions & 0 deletions __tests__/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { deepStrictEqual } from 'node:assert/strict';
import { checkSync } from 'recheck';

const defaultOptions = Object.freeze({
apiUrl: 'https://api.github.com',
isEarlyExit: true,
attemptLimits: 1000,
waitList: [],
Expand All @@ -21,6 +22,7 @@ const defaultOptions = Object.freeze({

test('Options keep given values', () => {
optionsEqual({
apiUrl: 'https://api.github.com',
isEarlyExit: true,
attemptLimits: 1000,
waitList: [],
Expand Down
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ inputs:
description: 'The GITHUB_TOKEN secret'
required: false
default: ${{ github.token }}
github-api-url:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
github-api-url:
github-graphql-url:

I think this will be better to clarify the difference of REST endpoints

Copy link
Contributor Author

@sochotnicky sochotnicky Oct 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case - octokit graphql actually does "magic" to figure out graphql endpoint from rest endpoint here:
https://github.com/octokit/graphql.js/blob/main/src/graphql.ts#L67

If we pass graphql endpoint as baseUrl it would produce url such as "https://ghe.example.net/graphql/graphql".

So in this case we need to leave it as github-api-url. Or if you'd prefer github-rest-api-url ?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/octokit/graphql.js/blob/9c0643d34f36ed558e55193438d7aa8b031ca43d/src/graphql.ts#L23
https://github.com/octokit/graphql.js/blob/9c0643d34f36ed558e55193438d7aa8b031ca43d/src/graphql.ts#L67-L72

const GHES_V3_SUFFIX_REGEX = /\/api\/v3\/?$/;

  // workaround for GitHub Enterprise baseUrl set with /api/v3 suffix
  // https://github.com/octokit/auth-app.js/issues/111#issuecomment-657610451
  const baseUrl = parsedOptions.baseUrl || request.endpoint.DEFAULTS.baseUrl;
  if (GHES_V3_SUFFIX_REGEX.test(baseUrl)) {
    requestOptions.url = baseUrl.replace(GHES_V3_SUFFIX_REGEX, "/api/graphql");
  }

If we pass graphql endpoint as baseUrl it would produce url such as "https://ghe.example.net/graphql/graphql".

The workaround seems to focus only on the /api/v3 suffix. Did it actually append nested graphql suffix for your URL?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we have https://ghe.example.com/api/v3 for the api_url. When I use it I end with following error (redacted):

const error2 = new import_request_error.RequestError(toErrorMessage(data), status, {
  RequestError [HttpError]: Not Found
      at main/dist/index.js:18839:26
      at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
      at async Object.next (main/dist/index.js:21920:32)
      at async Function.paginate (main/dist/index.js:21968:26)
      at async getCheckRunSummaries (main/dist/index.js:25927:55)
      at async fetchOtherRunStatus (main/dist/index.js:26018:27)
      at async run (main/dist/index.js:26183:20) {
    status: 404,
    response: {
      url: 'https://ghe.example.com/api/graphql/graphql',
      status: 404,

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#618 (comment)

github.api_url may raise the error, but your ${{ github.graphql_url }} have same suffix? As I understand it, GitHub GraphQL API is v4. (I may not know GHE backgrounds 🙇‍♂️ )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In our GHE instance ${{ github.graphql_url }} resolves to something like: https://ghe.example.net/api/graphql (which is correct endpoint for it). So the octokit graphql takes: https://ghe.example.net/api/v3 and replaces api/v3 with api/graphql. Otherwise it appends graphql (assuming baseURL is api.github.com). Which is why we need to just give octokit ${{ github.api_url }}. It figures out graphql endoint on its own.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for sharing! I'm reading how some other actions are handling this ....

https://github.com/google-github-actions/release-please-action/pull/532

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

description: 'Github API URL'
required: true
default: 'https://api.github.com'
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
default: 'https://api.github.com'
default: '${{ github.api_url }}'

Can we use placeholder for getting default value?

wait-seconds-before-first-polling:
description: 'Wait this seconds before first polling'
required: false
Expand Down
9 changes: 6 additions & 3 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32618,6 +32618,7 @@ var WaitList = z2.array(WaitFilterCondition).readonly();
var SkipList = z2.array(SkipFilterCondition).readonly();
var retryMethods = z2.enum(["exponential_backoff", "equal_intervals"]);
var Options = z2.object({
apiUrl: z2.string().url(),
waitList: WaitList,
skipList: SkipList,
initialDuration: Duration,
Expand Down Expand Up @@ -32687,7 +32688,9 @@ function parseInput() {
const isEarlyExit = (0, import_core.getBooleanInput)("early-exit", { required: true, trimWhitespace: true });
const shouldSkipSameWorkflow = (0, import_core.getBooleanInput)("skip-same-workflow", { required: true, trimWhitespace: true });
const isDryRun = (0, import_core.getBooleanInput)("dry-run", { required: true, trimWhitespace: true });
const apiUrl = (0, import_core.getInput)("github-api-url", { required: true, trimWhitespace: true });
const options = Options.parse({
apiUrl,
initialDuration: Durationable.parse({ seconds: waitSecondsBeforeFirstPolling }),
leastInterval: Durationable.parse({ seconds: minIntervalSeconds }),
retryMethod,
Expand Down Expand Up @@ -33808,8 +33811,8 @@ function paginateGraphQL(octokit) {

// src/github-api.ts
var PaginatableOctokit = Octokit.plugin(paginateGraphQL);
async function fetchChecks(token, trigger) {
const octokit = new PaginatableOctokit({ auth: token });
async function fetchChecks(apiUrl, token, trigger) {
const octokit = new PaginatableOctokit({ auth: token, baseUrl: apiUrl });
const { repository: { object: { checkSuites } } } = await octokit.graphql.paginate(
/* GraphQL */
`
Expand Down Expand Up @@ -34167,7 +34170,7 @@ async function run() {
}
const elapsed = mr.Duration.from({ milliseconds: Math.ceil(performance.now() - startedAt) });
(0, import_core3.startGroup)(`Polling ${attempts}: ${(/* @__PURE__ */ new Date()).toISOString()} # total elapsed ${readableDuration(elapsed)}`);
const checks = await fetchChecks(githubToken, trigger);
const checks = await fetchChecks(options.apiUrl, githubToken, trigger);
const report = generateReport(
getSummaries(checks, trigger),
trigger,
Expand Down
3 changes: 2 additions & 1 deletion src/github-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import { Check, Trigger } from './schema.ts';
const PaginatableOctokit = Octokit.plugin(paginateGraphQL);

export async function fetchChecks(
apiUrl: string,
token: string,
trigger: Trigger,
): Promise<Check[]> {
const octokit = new PaginatableOctokit({ auth: token });
const octokit = new PaginatableOctokit({ auth: token, baseUrl: apiUrl });
const { repository: { object: { checkSuites } } } = await octokit.graphql.paginate<
{ repository: { object: { checkSuites: Commit['checkSuites'] } } }
>(
Expand Down
2 changes: 2 additions & 0 deletions src/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@ export function parseInput(): { trigger: Trigger; options: Options; githubToken:
const isEarlyExit = getBooleanInput('early-exit', { required: true, trimWhitespace: true });
const shouldSkipSameWorkflow = getBooleanInput('skip-same-workflow', { required: true, trimWhitespace: true });
const isDryRun = getBooleanInput('dry-run', { required: true, trimWhitespace: true });
const apiUrl = getInput('github-api-url', { required: true, trimWhitespace: true });

const options = Options.parse({
apiUrl,
initialDuration: Durationable.parse({ seconds: waitSecondsBeforeFirstPolling }),
leastInterval: Durationable.parse({ seconds: minIntervalSeconds }),
retryMethod,
Expand Down
2 changes: 1 addition & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ async function run(): Promise<void> {
// Put getting elapsed time before of fetchChecks to keep accuracy of the purpose
const elapsed = Temporal.Duration.from({ milliseconds: Math.ceil(performance.now() - startedAt) });
startGroup(`Polling ${attempts}: ${(new Date()).toISOString()} # total elapsed ${readableDuration(elapsed)}`);
const checks = await fetchChecks(githubToken, trigger);
const checks = await fetchChecks(options.apiUrl, githubToken, trigger);

const report = generateReport(
getSummaries(checks, trigger),
Expand Down
1 change: 1 addition & 0 deletions src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export type RetryMethod = z.infer<typeof retryMethods>;
// - Do not specify default values with zod. That is an action.yml role
// - Do not include secrets here, for example githubToken. See https://github.com/colinhacks/zod/issues/1783
export const Options = z.object({
apiUrl: z.string().url(),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know zod provides url validations 👍

waitList: WaitList,
skipList: SkipList,
initialDuration: Duration,
Expand Down