diff --git a/frontend/packages/git-service/src/services/__tests__/bitbucket-service.spec.ts b/frontend/packages/git-service/src/services/__tests__/bitbucket-service.spec.ts index c39539d36e9..feb24006660 100644 --- a/frontend/packages/git-service/src/services/__tests__/bitbucket-service.spec.ts +++ b/frontend/packages/git-service/src/services/__tests__/bitbucket-service.spec.ts @@ -294,4 +294,87 @@ describe('Bitbucket Service', () => { nockDone(); }); }); + + it('should preserve scheme and port for Bitbucket Server API calls', async () => { + const gitSource: GitSource = { + url: 'http://bb.example.com:7990/scm/PROJ/repo.git', + }; + const gitService = new BitbucketService(gitSource); + + const scope = nock('http://bb.example.com:7990') + .get('/rest/api/1.0/projects/PROJ/repos/repo') + .reply(200, { slug: 'repo' }); + + const status = await gitService.isRepoReachable(); + expect(status).toEqual(RepoStatus.Reachable); + scope.done(); + }); + + describe('getRepoMetadata - Protocol and Port Handling', () => { + it('should use HTTPS protocol for standard Bitbucket URL', () => { + const gitSource: GitSource = { url: 'https://bitbucket.org/owner/repo' }; + const gitService = new BitbucketService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('https://bitbucket.org'); + }); + + it('should preserve HTTP protocol', () => { + const gitSource: GitSource = { url: 'http://bitbucket.example.com/owner/repo' }; + const gitService = new BitbucketService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('http://bitbucket.example.com'); + }); + + it('should preserve custom port with HTTPS', () => { + const gitSource: GitSource = { url: 'https://bitbucket.example.com:8443/owner/repo' }; + const gitService = new BitbucketService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('https://bitbucket.example.com:8443'); + }); + + it('should preserve custom port with HTTP', () => { + const gitSource: GitSource = { url: 'http://bitbucket.example.com:8080/owner/repo' }; + const gitService = new BitbucketService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('http://bitbucket.example.com:8080'); + }); + + it('should default to HTTPS for SSH URLs and preserve port', () => { + const gitSource: GitSource = { url: 'git@bitbucket.example.com:2222/owner/repo.git' }; + const gitService = new BitbucketService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('https://bitbucket.example.com:2222'); + }); + + it('should default to HTTPS for git:// protocol URLs', () => { + const gitSource: GitSource = { url: 'git://bitbucket.example.com/owner/repo.git' }; + const gitService = new BitbucketService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('https://bitbucket.example.com'); + }); + + it('should preserve non-standard port regardless of original protocol', () => { + const gitSource: GitSource = { url: 'ssh://git@bitbucket.example.com:9999/owner/repo.git' }; + const gitService = new BitbucketService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('https://bitbucket.example.com:9999'); + }); + + it('should handle Bitbucket Server URL with custom port', () => { + const gitSource: GitSource = { + url: 'http://bb.example.com:7990/scm/proj/repo.git', + }; + const gitService = new BitbucketService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('http://bb.example.com:7990'); + }); + }); }); diff --git a/frontend/packages/git-service/src/services/__tests__/gitea-service.spec.ts b/frontend/packages/git-service/src/services/__tests__/gitea-service.spec.ts index 01f509f3cd0..51b4bc47eb6 100644 --- a/frontend/packages/git-service/src/services/__tests__/gitea-service.spec.ts +++ b/frontend/packages/git-service/src/services/__tests__/gitea-service.spec.ts @@ -281,4 +281,62 @@ describe('Gitea Service', () => { nockDone(); }); }); + + describe('getRepoMetadata - Protocol and Port Handling', () => { + it('should use HTTPS protocol for standard Gitea URL', () => { + const gitSource: GitSource = { url: 'https://gitea.com/owner/repo' }; + const gitService = new GiteaService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('https://gitea.com'); + }); + + it('should preserve HTTP protocol', () => { + const gitSource: GitSource = { url: 'http://gitea.example.com/owner/repo' }; + const gitService = new GiteaService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('http://gitea.example.com'); + }); + + it('should preserve custom port with HTTPS', () => { + const gitSource: GitSource = { url: 'https://gitea.example.com:8443/owner/repo' }; + const gitService = new GiteaService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('https://gitea.example.com:8443'); + }); + + it('should preserve custom port with HTTP', () => { + const gitSource: GitSource = { url: 'http://gitea.example.com:8080/owner/repo' }; + const gitService = new GiteaService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('http://gitea.example.com:8080'); + }); + + it('should default to HTTPS for SSH URLs and preserve port', () => { + const gitSource: GitSource = { url: 'git@gitea.example.com:2222/owner/repo.git' }; + const gitService = new GiteaService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('https://gitea.example.com:2222'); + }); + + it('should default to HTTPS for git:// protocol URLs', () => { + const gitSource: GitSource = { url: 'git://gitea.example.com/owner/repo.git' }; + const gitService = new GiteaService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('https://gitea.example.com'); + }); + + it('should preserve non-standard port regardless of original protocol', () => { + const gitSource: GitSource = { url: 'ssh://git@gitea.example.com:9999/owner/repo.git' }; + const gitService = new GiteaService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('https://gitea.example.com:9999'); + }); + }); }); diff --git a/frontend/packages/git-service/src/services/__tests__/github-service.spec.ts b/frontend/packages/git-service/src/services/__tests__/github-service.spec.ts index e59f925cc76..8f364695b7c 100644 --- a/frontend/packages/git-service/src/services/__tests__/github-service.spec.ts +++ b/frontend/packages/git-service/src/services/__tests__/github-service.spec.ts @@ -265,6 +265,17 @@ describe('Github Service', () => { scope.done(); }); + it('should preserve scheme and port when making API call to specified hostname', async () => { + const gitSource: GitSource = { url: 'http://example.com:3000/test/repo' }; + const gitService = new GithubService(gitSource); + + const scope = nock('http://example.com:3000/api/v3').get('/repos/test/repo').reply(200); + + const status = await gitService.isRepoReachable(); + expect(status).toEqual(RepoStatus.Reachable); + scope.done(); + }); + it('should detect .tekton folder', () => { const gitSource = { url: 'https://github.com/Lucifergene/oc-pipe', @@ -324,4 +335,62 @@ describe('Github Service', () => { nockDone(); }); }); + + describe('getRepoMetadata - Protocol and Port Handling', () => { + it('should use HTTPS protocol for standard GitHub URL', () => { + const gitSource: GitSource = { url: 'https://github.com/owner/repo' }; + const gitService = new GithubService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('https://github.com'); + }); + + it('should preserve HTTP protocol', () => { + const gitSource: GitSource = { url: 'http://github.example.com/owner/repo' }; + const gitService = new GithubService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('http://github.example.com'); + }); + + it('should preserve custom port with HTTPS', () => { + const gitSource: GitSource = { url: 'https://github.example.com:8443/owner/repo' }; + const gitService = new GithubService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('https://github.example.com:8443'); + }); + + it('should preserve custom port with HTTP', () => { + const gitSource: GitSource = { url: 'http://github.example.com:8080/owner/repo' }; + const gitService = new GithubService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('http://github.example.com:8080'); + }); + + it('should default to HTTPS for SSH URLs and preserve port', () => { + const gitSource: GitSource = { url: 'git@github.example.com:2222/owner/repo.git' }; + const gitService = new GithubService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('https://github.example.com:2222'); + }); + + it('should default to HTTPS for git:// protocol URLs', () => { + const gitSource: GitSource = { url: 'git://github.example.com/owner/repo.git' }; + const gitService = new GithubService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('https://github.example.com'); + }); + + it('should preserve non-standard port regardless of original protocol', () => { + const gitSource: GitSource = { url: 'ssh://git@github.example.com:9999/owner/repo.git' }; + const gitService = new GithubService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('https://github.example.com:9999'); + }); + }); }); diff --git a/frontend/packages/git-service/src/services/__tests__/gitlab-service.spec.ts b/frontend/packages/git-service/src/services/__tests__/gitlab-service.spec.ts index 8930e001abb..46754b5dfbd 100644 --- a/frontend/packages/git-service/src/services/__tests__/gitlab-service.spec.ts +++ b/frontend/packages/git-service/src/services/__tests__/gitlab-service.spec.ts @@ -316,4 +316,84 @@ describe('Gitlab Service', () => { expect(status).toEqual(RepoStatus.Reachable); scope.done(); }); + + it('should preserve scheme and port when making API call to specified hostname', async () => { + const gitSource: GitSource = { url: 'http://example.com:3000/test/repo' }; + const gitService = new GitlabService(gitSource); + + const scope = nock('http://example.com:3000/api/v4') + .get('/projects/test%2Frepo') + // eslint-disable-next-line @typescript-eslint/naming-convention + .reply(200, { path_with_namespace: 'test/repo' }); + + const status = await gitService.isRepoReachable(); + expect(status).toEqual(RepoStatus.Reachable); + scope.done(); + }); + + describe('getRepoMetadata - Protocol and Port Handling', () => { + it('should use HTTPS protocol for standard GitLab URL', () => { + const gitSource: GitSource = { url: 'https://gitlab.com/owner/repo' }; + const gitService = new GitlabService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('https://gitlab.com'); + }); + + it('should preserve HTTP protocol', () => { + const gitSource: GitSource = { url: 'http://gitlab.example.com/owner/repo' }; + const gitService = new GitlabService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('http://gitlab.example.com'); + }); + + it('should preserve custom port with HTTPS', () => { + const gitSource: GitSource = { url: 'https://gitlab.example.com:8443/owner/repo' }; + const gitService = new GitlabService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('https://gitlab.example.com:8443'); + }); + + it('should preserve custom port with HTTP', () => { + const gitSource: GitSource = { url: 'http://gitlab.example.com:8080/owner/repo' }; + const gitService = new GitlabService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('http://gitlab.example.com:8080'); + }); + + it('should default to HTTPS for SSH URLs and preserve port', () => { + const gitSource: GitSource = { url: 'git@gitlab.example.com:2222/owner/repo.git' }; + const gitService = new GitlabService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('https://gitlab.example.com:2222'); + }); + + it('should default to HTTPS for git:// protocol URLs', () => { + const gitSource: GitSource = { url: 'git://gitlab.example.com/owner/repo.git' }; + const gitService = new GitlabService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('https://gitlab.example.com'); + }); + + it('should preserve non-standard port regardless of original protocol', () => { + const gitSource: GitSource = { url: 'ssh://git@gitlab.example.com:9999/owner/repo.git' }; + const gitService = new GitlabService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('https://gitlab.example.com:9999'); + }); + + it('should handle subdomains with custom port', () => { + const gitSource: GitSource = { url: 'https://version.helsinki.fi:3000/owner/repo' }; + const gitService = new GitlabService(gitSource); + const metadata = gitService.getRepoMetadata(); + + expect(metadata.host).toBe('https://version.helsinki.fi:3000'); + }); + }); }); diff --git a/frontend/packages/git-service/src/services/bitbucket-service.ts b/frontend/packages/git-service/src/services/bitbucket-service.ts index ef564ef468a..46c72702650 100644 --- a/frontend/packages/git-service/src/services/bitbucket-service.ts +++ b/frontend/packages/git-service/src/services/bitbucket-service.ts @@ -1,5 +1,5 @@ +import * as GitUrlParse from 'git-url-parse'; import { Base64 } from 'js-base64'; -import * as ParseBitbucketUrl from 'parse-bitbucket-url'; import { consoleFetchJSON } from '@console/dynamic-plugin-sdk/src/lib-core'; import { DevConsoleEndpointResponse } from '@console/shared/src'; import { @@ -41,8 +41,8 @@ export class BitbucketService extends BaseService { constructor(gitsource: GitSource) { super(gitsource); this.metadata = this.getRepoMetadata(); - if (this.metadata.host !== 'bitbucket.org') { - this.baseURL = `https://${this.metadata.host}/rest/api/1.0`; + if (!this.metadata.host.includes('bitbucket.org')) { + this.baseURL = `${this.metadata.host}/rest/api/1.0`; this.isServer = true; } } @@ -88,8 +88,14 @@ export class BitbucketService extends BaseService { }; getRepoMetadata = (): RepoMetadata => { - const { name, owner, host } = ParseBitbucketUrl(this.gitsource.url); + const { name, owner, resource, protocols, port } = GitUrlParse(this.gitsource.url); const contextDir = this.gitsource.contextDir?.replace(/\/$/, '') || ''; + const rawProtocol = protocols?.[0]; + const isHttpProtocol = rawProtocol === 'http' || rawProtocol === 'https'; + const protocol = isHttpProtocol ? rawProtocol : 'https'; + + const host = port ? `${protocol}://${resource}:${port}` : `${protocol}://${resource}`; + return { repoName: name, owner, diff --git a/frontend/packages/git-service/src/services/gitea-service.ts b/frontend/packages/git-service/src/services/gitea-service.ts index 05cfebd070e..2062521571c 100644 --- a/frontend/packages/git-service/src/services/gitea-service.ts +++ b/frontend/packages/git-service/src/services/gitea-service.ts @@ -62,9 +62,16 @@ export class GiteaService extends BaseService { }; getRepoMetadata = (): RepoMetadata => { - const { name, owner, resource, full_name: fullName } = GitUrlParse(this.gitsource.url); + const { name, owner, resource, protocols, port, full_name: fullName } = GitUrlParse( + this.gitsource.url, + ); const contextDir = this.gitsource.contextDir?.replace(/\/$/, '') || ''; - const host = `https://${resource}`; + const rawProtocol = protocols?.[0]; + const isHttpProtocol = rawProtocol === 'http' || rawProtocol === 'https'; + const protocol = isHttpProtocol ? rawProtocol : 'https'; + + const host = port ? `${protocol}://${resource}:${port}` : `${protocol}://${resource}`; + return { repoName: name, owner, diff --git a/frontend/packages/git-service/src/services/github-service.ts b/frontend/packages/git-service/src/services/github-service.ts index d59deb61f91..757e0807c03 100644 --- a/frontend/packages/git-service/src/services/github-service.ts +++ b/frontend/packages/git-service/src/services/github-service.ts @@ -44,8 +44,9 @@ export class GithubService extends BaseService { super(gitsource); const authOpts = this.getAuthProvider(); this.metadata = this.getRepoMetadata(); - const baseUrl = - this.metadata.host === 'github.com' ? null : `https://${this.metadata.host}/api/v3`; + const { resource, port } = GitUrlParse(this.gitsource.url); + const isGithubDotCom = resource === 'github.com' && !port; + const baseUrl = isGithubDotCom ? null : `${this.metadata.host}/api/v3`; this.client = new Octokit({ ...authOpts, baseUrl }); } @@ -60,13 +61,19 @@ export class GithubService extends BaseService { } }; - protected getRepoMetadata = (): RepoMetadata => { - const { name, owner, source } = GitUrlParse(this.gitsource.url); + getRepoMetadata = (): RepoMetadata => { + const { name, owner, protocols, port, resource } = GitUrlParse(this.gitsource.url); + const rawProtocol = protocols?.[0]; + const isHttpProtocol = rawProtocol === 'http' || rawProtocol === 'https'; + const protocol = isHttpProtocol ? rawProtocol : 'https'; + + const host = port ? `${protocol}://${resource}:${port}` : `${protocol}://${resource}`; + const contextDir = this.gitsource.contextDir?.replace(/\/$/, '') || ''; return { repoName: name, owner, - host: source, + host, defaultBranch: this.gitsource.ref, contextDir, devfilePath: this.gitsource.devfilePath, @@ -175,10 +182,11 @@ export class GithubService extends BaseService { }, events: ['push', 'pull_request'], }; - const AddWebhookBaseURL = - this.metadata.host === 'github.com' - ? `https://api.github.com` - : `https://${this.metadata.host}/api/v3`; + const { resource, port } = GitUrlParse(this.gitsource.url); + const isGithubDotCom = resource === 'github.com' && !port; + const AddWebhookBaseURL = isGithubDotCom + ? `https://api.github.com` + : `${this.metadata.host}/api/v3`; const webhookRequestBody: GithubWebhookRequest = { headers, diff --git a/frontend/packages/git-service/src/services/gitlab-service.ts b/frontend/packages/git-service/src/services/gitlab-service.ts index 26acf44c63c..17fee44c9a4 100644 --- a/frontend/packages/git-service/src/services/gitlab-service.ts +++ b/frontend/packages/git-service/src/services/gitlab-service.ts @@ -78,9 +78,16 @@ export class GitlabService extends BaseService { }; getRepoMetadata(): RepoMetadata { - const { name, owner, resource, full_name: fullName } = GitUrlParse(this.gitsource.url); + const { name, owner, protocols, port, resource, full_name: fullName } = GitUrlParse( + this.gitsource.url, + ); const contextDir = removeLeadingSlash(this.gitsource.contextDir); - const host = `https://${resource}`; + const rawProtocol = protocols?.[0]; + const isHttpProtocol = rawProtocol === 'http' || rawProtocol === 'https'; + const protocol = isHttpProtocol ? rawProtocol : 'https'; + + const host = port ? `${protocol}://${resource}:${port}` : `${protocol}://${resource}`; + return { repoName: name, owner, diff --git a/frontend/packages/git-service/src/types/repo.ts b/frontend/packages/git-service/src/types/repo.ts index 9ffd53ffee0..77366d0fcf9 100644 --- a/frontend/packages/git-service/src/types/repo.ts +++ b/frontend/packages/git-service/src/types/repo.ts @@ -1,6 +1,25 @@ export interface RepoMetadata { repoName: string; owner: string; + /** + * The host URL including protocol and port. + * + * Format: `://[:]` + * + * Examples: + * - `https://github.com` + * - `http://gitlab.example.com:8080` + * - `https://bitbucket.example.com:7990` + * + * **Breaking Change:** Previously, this field contained only the hostname + * (e.g., `github.com`). Now it includes the full protocol and port to properly + * support enterprise Git instances with custom protocols and ports. + * + * If you need just the hostname, parse it from this field: + * ```typescript + * const hostname = new URL(metadata.host).hostname; + * ``` + */ host: string; defaultBranch?: string; fullName?: string;