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

[Bug](solved?): Forgejo/Gitea failing due to bad API URL #3569

Open
jiriks74 opened this issue Dec 15, 2024 · 6 comments
Open

[Bug](solved?): Forgejo/Gitea failing due to bad API URL #3569

jiriks74 opened this issue Dec 15, 2024 · 6 comments

Comments

@jiriks74
Copy link

Subject of the issue

Running this action on Forgejo/Gitea results in the following error:

::error::Not found.%0A

Debugging this lead me to the following URL definition:

https://github.com/peter-evans/create-pull-request/blob/16e0059bfd236716f0191bfcfa63d9ded4cf325f/dist/index.js#L1287

Changing this

From 9791a4f146d5369620ea3b934822e717d3d90034 Mon Sep 17 00:00:00 2001
From: jiriks74 <[email protected]>
Date: Sun, 15 Dec 2024 02:37:18 +0100
Subject: [PATCH] fix: Use the `v1` api as `v3` is not available on Forgejo.

---
 dist/index.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/dist/index.js b/dist/index.js
index b555e94..8cbfb72 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -1284,7 +1284,7 @@ class GitHubHelper {
             options.auth = `${token}`;
         }
         if (githubServerHostname !== 'github.com') {
-            options.baseUrl = `https://${githubServerHostname}/api/v3`;
+            options.baseUrl = `https://${githubServerHostname}/api/v1`;
         }
         else {
             options.baseUrl = 'https://api.github.com';

resolves the issue and results in a working Action:

Steps to reproduce

  1. Use Forgejo/Gitea
  2. Create a simple Action modifying some files
  3. Use this Action for pushing the changes to the repository and creating a PR
  4. Run the new Action
  5. See the ::error::Not found.%0A error
@jiriks74
Copy link
Author

We can possibly figure out the host by looking at the /api/swagger# path. I'll be asking devs if there's some better way to do this.

@jiriks74
Copy link
Author

jiriks74 commented Dec 15, 2024

Ok a better way is to check /api/v1/version. It returns the version using the following format:

{"version":"1.23.0+dev-789-g18061af490"}

@jiriks74
Copy link
Author

jiriks74 commented Dec 15, 2024

If we want to check specifically for Forgejo we can use api/forgejo/v1/version:

{"version":"9.0.2+gitea-1.22.0"}

@jiriks74
Copy link
Author

jiriks74 commented Dec 15, 2024

I made a simple proof of concept. Keep in mind that I don't know JavaScript so it probably needs some revision:

Code
async function determineApiBaseUrl(hostname) {
    if ( hostname == 'github.com' ){
      console.log(`Valid API path detected: https://api.github.com`);
      return "https://api.github.com";
    }

    const baseUrl = `https://${hostname}`;
    const possiblePaths = ['/api/v4/version', '/api/forgejo/v1/version', '/api/v1/version' ];

    for (const path of possiblePaths) {
      try {
            const url = `${baseUrl}${path}`;
            const response = await fetch(url, { method: 'GET', redirect: 'manual' }); // GitLab redirects `/api/forgejo/v1/version to
                                                                                      // login prompt so we get response 200
                                                                                      // if we allow the redirect. Now we get 302.

            const contentType = response.headers.get('Content-Type') || '';
            if (
                (response.ok || [401, 403].includes(response.status)) && // Check for valid API status codes
                contentType.includes('application/json') // Ensure it's returning JSON
            ) {
                console.log(`Valid API path detected: ${path.includes('/version') ? url.replace('/version', '') : url}`);
                return path.includes('/version') ? url.replace('/version', '') : url;
            }
        } catch (error) {
        }
    }

    throw new Error(`Unable to determine API base URL for hostname: ${hostname}`);
}

(async () => {
    try {
        const hostname = "github.com";
        const apiBaseUrl = await determineApiBaseUrl(hostname);
    } catch (error) {
        console.error(error.message);
        process.exit(1); // Exit with an error code if no API path is found
    }
})();


(async () => {
    try {
        const hostname = "codeberg.org";
        const apiBaseUrl = await determineApiBaseUrl(hostname);
    } catch (error) {
        console.error(error.message);
        process.exit(1); // Exit with an error code if no API path is found
    }
})();

(async () => {
    try {
        const hostname = "demo.gitea.com";
        const apiBaseUrl = await determineApiBaseUrl(hostname);
    } catch (error) {
        console.error(error.message);
        process.exit(1); // Exit with an error code if no API path is found
    }
})();

(async () => {
    try {
        const hostname = "gitlab.com";
        const apiBaseUrl = await determineApiBaseUrl(hostname);
    } catch (error) {
        console.error(error.message);
        process.exit(1); // Exit with an error code if no API path is found
    }
})();

I get the following output when running this code:

Valid API path detected: https://api.github.com
Valid API path detected: https://codeberg.org/api/v1
Valid API path detected: https://gitlab.com/api/v4
Valid API path detected: https://demo.gitea.com/api/v1

and I get the following when the API URL isn't recognized:

Unable to determine API base URL for hostname: example.com

so the end user knows that the Action failed because of a bad API URL.

Maybe we could evern let the user define a custom API URL in the Action inputs if we aren't able to detect it.

@jiriks74
Copy link
Author

jiriks74 commented Dec 16, 2024

With what I found online I was able to create a working patch. I'm happy to open a PR if you find it good enough.

Patch
diff --git a/src/create-pull-request.ts b/src/create-pull-request.ts
index 9076b7d..a8971a7 100644
--- a/src/create-pull-request.ts
+++ b/src/create-pull-request.ts
@@ -51,8 +51,10 @@ export async function createPullRequest(inputs: Inputs): Promise<void> {
     core.startGroup('Determining the base and head repositories')
     const baseRemote = gitConfigHelper.getGitRemote()
     // Init the GitHub clients
-    const ghBranch = new GitHubHelper(baseRemote.hostname, inputs.branchToken)
-    const ghPull = new GitHubHelper(baseRemote.hostname, inputs.token)
+    const apiUrl = await GitHubHelper.determineApiUrl(baseRemote.hostname);
+    core.info(`Using API base URL: ${apiUrl}`);
+    const ghBranch = new GitHubHelper(apiUrl, inputs.branchToken)
+    const ghPull = new GitHubHelper(apiUrl, inputs.token)
     // Determine the head repository; the target for the pull request branch
     const branchRemoteName = inputs.pushToFork ? 'fork' : 'origin'
     const branchRepository = inputs.pushToFork
diff --git a/src/github-helper.ts b/src/github-helper.ts
index 82a9296..15efc16 100644
--- a/src/github-helper.ts
+++ b/src/github-helper.ts
@@ -41,20 +41,49 @@ type TreeObject = {
 export class GitHubHelper {
   private octokit: InstanceType<typeof Octokit>
 
-  constructor(githubServerHostname: string, token: string) {
+  constructor(apiUrl: string, token: string) {
     const options: OctokitOptions = {}
     if (token) {
       options.auth = `${token}`
     }
-    if (githubServerHostname !== 'github.com') {
-      options.baseUrl = `https://${githubServerHostname}/api/v3`
-    } else {
-      options.baseUrl = 'https://api.github.com'
-    }
+    options.baseUrl = apiUrl;
     options.throttle = throttleOptions
     this.octokit = new Octokit(options)
   }
 
+  static async determineApiUrl(hostname: string): Promise<string> {
+    if (hostname === 'github.com') {
+      return "https://api.github.com";
+    }
+
+    const baseUrl = `https://${hostname}`;
+    const possiblePaths = ['/api/v4/version', '/api/forgejo/v1/version', '/api/v1/version'];
+
+    for (const path of possiblePaths) {
+      try {
+        const url = `${baseUrl}${path}`;
+        const response = await fetch(url, { method: 'GET', redirect: 'manual' }); // GitLab redirects
+                                                                                  // invalid API paths
+                                                                                  // to login prompt
+                                                                                  // which returns 200
+
+        const contentType = response.headers.get('Content-Type') || '';
+        if (
+          (response.ok || [401, 403].includes(response.status)) && // We might get 401, 403
+                                                                   // as we're unauthorised
+          contentType.includes('application/json')
+        ) {
+          return path.includes('/version') ? url.replace('/version', '') : url;
+        }
+
+      } catch (error) {
+          // Ignore errors and try the next path
+      }
+    }
+
+    throw new Error(`Unable to determine API base URL for hostname: ${hostname}`);
+  }
+
   private parseRepository(repository: string): Repository {
     const [owner, repo] = repository.split('/')
     return {

I've tested the function locally with the following code

determineApiUrl("github.com")
  .then(url => console.log(url))
  .catch(error => console.error(error));
determineApiUrl("gitlab.com")
  .then(url => console.log(url))
  .catch(error => console.error(error));
determineApiUrl("codeberg.org")
  .then(url => console.log(url))
  .catch(error => console.error(error));
determineApiUrl("demo.gitea.com")
  .then(url => console.log(url))
  .catch(error => console.error(error));

producing this output

https://api.github.com
https://codeberg.org/api/v1
https://gitlab.com/api/v4
https://demo.gitea.com/api/v1

@jiriks74 jiriks74 changed the title Forgejo/Gitea pull requests failing due to bad API URL definition [Bug](solved?): Forgejo/Gitea failing due to bad API URL Dec 16, 2024
@peter-evans
Copy link
Owner

Hi @jiriks74

Thank you for raising this and your effort to solve it! Very interesting.

This action supports many use cases, and I would be very surprised if they all work outside of GitHub's ecosystem. I'm not sure that I'm prepared to try and make this action work elsewhere and keep up with its maintenance.

You are welcome to fork this action and try and make a working version that covers the test cases. If you achieve it I would consider if it makes sense to merge it in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants