mirror of
https://github.com/docker/actions-toolkit.git
synced 2024-11-23 11:36:10 +08:00
Merge pull request #391 from crazy-max/buildkit-git
buildkit: git parseURL and parseRef funcs
This commit is contained in:
commit
2cb2c5573f
344
__tests__/buildkit/git.test.ts
Normal file
344
__tests__/buildkit/git.test.ts
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2024 actions-toolkit authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {beforeEach, describe, expect, jest, test} from '@jest/globals';
|
||||||
|
|
||||||
|
import {Git} from '../../src/buildkit/git';
|
||||||
|
|
||||||
|
import {GitRef, GitURL} from '../../src/types/buildkit/git';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('parseURL', () => {
|
||||||
|
// prettier-ignore
|
||||||
|
test.each([
|
||||||
|
[
|
||||||
|
'http://github.com/moby/buildkit',
|
||||||
|
{
|
||||||
|
scheme: 'http',
|
||||||
|
host: 'github.com',
|
||||||
|
path: '/moby/buildkit'
|
||||||
|
} as GitURL,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'https://github.com/moby/buildkit',
|
||||||
|
{
|
||||||
|
scheme: 'https',
|
||||||
|
host: 'github.com',
|
||||||
|
path: '/moby/buildkit'
|
||||||
|
} as GitURL,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'http://github.com/moby/buildkit#v1.0.0',
|
||||||
|
{
|
||||||
|
scheme: 'http',
|
||||||
|
host: 'github.com',
|
||||||
|
path: '/moby/buildkit',
|
||||||
|
fragment: {
|
||||||
|
ref: 'v1.0.0',
|
||||||
|
}
|
||||||
|
} as GitURL,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'http://github.com/moby/buildkit#v1.0.0:subdir',
|
||||||
|
{
|
||||||
|
scheme: 'http',
|
||||||
|
host: 'github.com',
|
||||||
|
path: '/moby/buildkit',
|
||||||
|
fragment: {
|
||||||
|
ref: 'v1.0.0',
|
||||||
|
subdir: 'subdir'
|
||||||
|
}
|
||||||
|
} as GitURL,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'http://foo:bar@github.com/moby/buildkit#v1.0.0',
|
||||||
|
{
|
||||||
|
scheme: 'http',
|
||||||
|
host: 'github.com',
|
||||||
|
path: '/moby/buildkit',
|
||||||
|
fragment: {
|
||||||
|
ref: 'v1.0.0',
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
username: 'foo',
|
||||||
|
password: 'bar',
|
||||||
|
passwordSet: true
|
||||||
|
}
|
||||||
|
} as GitURL,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'ssh://git@github.com/moby/buildkit.git',
|
||||||
|
{
|
||||||
|
scheme: 'ssh',
|
||||||
|
host: 'github.com',
|
||||||
|
path: '/moby/buildkit.git',
|
||||||
|
user: {
|
||||||
|
username: 'git',
|
||||||
|
password: '',
|
||||||
|
passwordSet: false
|
||||||
|
}
|
||||||
|
} as GitURL,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'ssh://git@github.com:22/moby/buildkit.git',
|
||||||
|
{
|
||||||
|
scheme: 'ssh',
|
||||||
|
host: 'github.com:22',
|
||||||
|
path: '/moby/buildkit.git',
|
||||||
|
user: {
|
||||||
|
username: 'git',
|
||||||
|
password: '',
|
||||||
|
passwordSet: false
|
||||||
|
}
|
||||||
|
} as GitURL,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
// TODO: handle SCP-style URLs
|
||||||
|
// [
|
||||||
|
// 'git@github.com:moby/buildkit.git',
|
||||||
|
// {
|
||||||
|
// scheme: 'ssh',
|
||||||
|
// host: 'github.com:22',
|
||||||
|
// path: 'moby/buildkit.git',
|
||||||
|
// user: {
|
||||||
|
// username: 'git',
|
||||||
|
// password: '',
|
||||||
|
// passwordSet: false
|
||||||
|
// }
|
||||||
|
// } as GitURL,
|
||||||
|
// false
|
||||||
|
// ],
|
||||||
|
[
|
||||||
|
'ssh://root@subdomain.example.hostname:2222/root/my/really/weird/path/foo.git',
|
||||||
|
{
|
||||||
|
scheme: 'ssh',
|
||||||
|
host: 'subdomain.example.hostname:2222',
|
||||||
|
path: '/root/my/really/weird/path/foo.git',
|
||||||
|
user: {
|
||||||
|
username: 'root',
|
||||||
|
password: '',
|
||||||
|
passwordSet: false
|
||||||
|
}
|
||||||
|
} as GitURL,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'git://host.xz:1234/path/to/repo.git',
|
||||||
|
{
|
||||||
|
scheme: 'git',
|
||||||
|
host: 'host.xz:1234',
|
||||||
|
path: '/path/to/repo.git',
|
||||||
|
} as GitURL,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'ssh://someuser@192.168.0.123:456/~/repo-in-my-home-dir.git',
|
||||||
|
{
|
||||||
|
scheme: 'ssh',
|
||||||
|
host: '192.168.0.123:456',
|
||||||
|
path: '/~/repo-in-my-home-dir.git',
|
||||||
|
user: {
|
||||||
|
username: 'someuser',
|
||||||
|
password: '',
|
||||||
|
passwordSet: false
|
||||||
|
}
|
||||||
|
} as GitURL,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'httpx://github.com/moby/buildkit',
|
||||||
|
{} as GitURL,
|
||||||
|
true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'HTTP://github.com/moby/buildkit',
|
||||||
|
{
|
||||||
|
scheme: 'http',
|
||||||
|
host: 'github.com',
|
||||||
|
path: '/moby/buildkit'
|
||||||
|
} as GitURL,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
])('given %p', async (ref: string, expected: GitURL, expectedErr: boolean) => {
|
||||||
|
try {
|
||||||
|
const got = Git.parseURL(ref);
|
||||||
|
expect(got.scheme).toEqual(expected.scheme);
|
||||||
|
expect(got.host).toEqual(expected.host);
|
||||||
|
expect(got.path).toEqual(expected.path);
|
||||||
|
expect(got.fragment).toEqual(expected.fragment);
|
||||||
|
expect(got.user?.username).toEqual(expected.user?.username);
|
||||||
|
expect(got.user?.password).toEqual(expected.user?.password);
|
||||||
|
expect(got.user?.passwordSet).toEqual(expected.user?.passwordSet);
|
||||||
|
} catch (err) {
|
||||||
|
if (!expectedErr) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line jest/no-conditional-expect
|
||||||
|
expect(expectedErr).toBeTruthy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('parseRef', () => {
|
||||||
|
// prettier-ignore
|
||||||
|
test.each([
|
||||||
|
[
|
||||||
|
'https://example.com/',
|
||||||
|
undefined
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'https://example.com/foo',
|
||||||
|
undefined
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'https://example.com/foo.git',
|
||||||
|
{
|
||||||
|
remote: 'https://example.com/foo.git',
|
||||||
|
shortName: 'foo'
|
||||||
|
} as GitRef
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'https://example.com/foo.git#deadbeef',
|
||||||
|
{
|
||||||
|
remote: 'https://example.com/foo.git',
|
||||||
|
shortName: 'foo',
|
||||||
|
commit: 'deadbeef'
|
||||||
|
} as GitRef
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'https://example.com/foo.git#release/1.2',
|
||||||
|
{
|
||||||
|
remote: 'https://example.com/foo.git',
|
||||||
|
shortName: 'foo',
|
||||||
|
commit: 'release/1.2'
|
||||||
|
} as GitRef
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'https://example.com/foo.git/',
|
||||||
|
undefined
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'https://example.com/foo.git.bar',
|
||||||
|
undefined
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'git://example.com/foo',
|
||||||
|
{
|
||||||
|
remote: 'git://example.com/foo',
|
||||||
|
shortName: 'foo',
|
||||||
|
unencryptedTCP: true
|
||||||
|
} as GitRef
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'github.com/moby/buildkit',
|
||||||
|
{
|
||||||
|
remote: 'github.com/moby/buildkit',
|
||||||
|
shortName: 'buildkit',
|
||||||
|
indistinguishableFromLocal: true
|
||||||
|
} as GitRef
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'custom.xyz/moby/buildkit.git',
|
||||||
|
undefined
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'https://github.com/moby/buildkit',
|
||||||
|
undefined
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'https://github.com/moby/buildkit.git',
|
||||||
|
{
|
||||||
|
remote: 'https://github.com/moby/buildkit.git',
|
||||||
|
shortName: 'buildkit',
|
||||||
|
} as GitRef
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'https://foo:bar@github.com/moby/buildkit.git',
|
||||||
|
{
|
||||||
|
remote: 'https://foo:bar@github.com/moby/buildkit.git',
|
||||||
|
shortName: 'buildkit',
|
||||||
|
} as GitRef
|
||||||
|
],
|
||||||
|
// TODO handle SCP-style URLs
|
||||||
|
// [
|
||||||
|
// 'git@github.com:moby/buildkit',
|
||||||
|
// {
|
||||||
|
// remote: 'git@github.com:moby/buildkit',
|
||||||
|
// shortName: 'buildkit',
|
||||||
|
// } as GitRef
|
||||||
|
// ],
|
||||||
|
// [
|
||||||
|
// 'git@github.com:moby/buildkit.git',
|
||||||
|
// {
|
||||||
|
// remote: 'git@github.com:moby/buildkit',
|
||||||
|
// shortName: 'buildkit',
|
||||||
|
// } as GitRef
|
||||||
|
// ],
|
||||||
|
// [
|
||||||
|
// 'git@bitbucket.org:atlassianlabs/atlassian-docker.git',
|
||||||
|
// {
|
||||||
|
// remote: 'git@bitbucket.org:atlassianlabs/atlassian-docker.git',
|
||||||
|
// shortName: 'atlassian-docker',
|
||||||
|
// } as GitRef
|
||||||
|
// ],
|
||||||
|
[
|
||||||
|
'https://github.com/foo/bar.git#baz/qux:quux/quuz',
|
||||||
|
{
|
||||||
|
remote: 'https://github.com/foo/bar.git',
|
||||||
|
shortName: 'bar',
|
||||||
|
commit: 'baz/qux',
|
||||||
|
subDir: 'quux/quuz',
|
||||||
|
} as GitRef
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'https://github.com/docker/docker.git#:myfolder',
|
||||||
|
{
|
||||||
|
remote: 'https://github.com/docker/docker.git',
|
||||||
|
shortName: 'docker',
|
||||||
|
subDir: 'myfolder',
|
||||||
|
commit: ''
|
||||||
|
} as GitRef
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'./.git',
|
||||||
|
undefined
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'.git',
|
||||||
|
undefined
|
||||||
|
],
|
||||||
|
])('given %p', async (ref: string, expected: GitRef | undefined) => {
|
||||||
|
try {
|
||||||
|
const got = Git.parseRef(ref);
|
||||||
|
expect(got).toEqual(expected);
|
||||||
|
} catch (err) {
|
||||||
|
if (expected) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line jest/no-conditional-expect
|
||||||
|
expect(expected).toBeUndefined();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
113
src/buildkit/git.ts
Normal file
113
src/buildkit/git.ts
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2024 actions-toolkit authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {GitRef, GitURL, GitURLFragment, URLUserInfo} from '../types/buildkit/git';
|
||||||
|
|
||||||
|
export class Git {
|
||||||
|
private static protoRegexp = new RegExp('^[a-zA-Z0-9]+://');
|
||||||
|
private static supportedProtos = {
|
||||||
|
http: {},
|
||||||
|
https: {},
|
||||||
|
ssh: {},
|
||||||
|
git: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://github.com/moby/buildkit/blob/2ec1338fc13f73b43f0b1b4f4678d7cd654bc86c/util/gitutil/git_url.go#L79
|
||||||
|
public static parseURL(remote: string): GitURL {
|
||||||
|
const match = remote.match(Git.protoRegexp);
|
||||||
|
if (match && match.length > 0) {
|
||||||
|
let proto = match[0].toLowerCase();
|
||||||
|
proto = proto.slice(0, proto.lastIndexOf('://'));
|
||||||
|
if (!(proto in Git.supportedProtos)) {
|
||||||
|
throw new Error(`Invalid protocol: ${proto}`);
|
||||||
|
}
|
||||||
|
return Git.fromURL(new URL(remote));
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Unknown protocol');
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/moby/buildkit/blob/2ec1338fc13f73b43f0b1b4f4678d7cd654bc86c/util/gitutil/git_url.go#L108
|
||||||
|
private static fromURL(url: URL): GitURL {
|
||||||
|
const withoutFragment = new URL(url.toString());
|
||||||
|
withoutFragment.hash = '';
|
||||||
|
|
||||||
|
let user: URLUserInfo | undefined;
|
||||||
|
if (url.username || url.password) {
|
||||||
|
user = {
|
||||||
|
username: url.username,
|
||||||
|
password: url.password,
|
||||||
|
passwordSet: url.password !== ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: handle SCP-style URLs
|
||||||
|
|
||||||
|
return {
|
||||||
|
scheme: url.protocol.slice(0, -1),
|
||||||
|
user: user,
|
||||||
|
host: `${url.hostname}${url.port ? ':' + url.port : ''}`,
|
||||||
|
path: url.pathname,
|
||||||
|
fragment: Git.splitGitFragment(url.hash),
|
||||||
|
remote: withoutFragment.toString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/moby/buildkit/blob/2ec1338fc13f73b43f0b1b4f4678d7cd654bc86c/util/gitutil/git_url.go#L69
|
||||||
|
private static splitGitFragment(fragment: string): GitURLFragment | undefined {
|
||||||
|
if (fragment === '') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const [ref, subdir] = fragment.slice(1).split(':');
|
||||||
|
return {
|
||||||
|
ref: ref,
|
||||||
|
subdir: subdir
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/moby/buildkit/blob/2ec1338fc13f73b43f0b1b4f4678d7cd654bc86c/util/gitutil/git_ref.go#L52
|
||||||
|
public static parseRef(ref: string): GitRef | undefined {
|
||||||
|
const res: GitRef = {};
|
||||||
|
let remote: GitURL;
|
||||||
|
if (ref.startsWith('./') || ref.startsWith('../')) {
|
||||||
|
throw new Error('Invalid argument');
|
||||||
|
} else if (ref.startsWith('github.com/')) {
|
||||||
|
res.indistinguishableFromLocal = true; // Deprecated
|
||||||
|
remote = Git.fromURL(new URL('https://' + ref));
|
||||||
|
} else {
|
||||||
|
remote = Git.parseURL(ref);
|
||||||
|
if (['http', 'git'].includes(remote.scheme)) {
|
||||||
|
res.unencryptedTCP = true; // Discouraged, but not deprecated
|
||||||
|
}
|
||||||
|
if (['http', 'https'].includes(remote.scheme) && !remote.path.endsWith('.git')) {
|
||||||
|
throw new Error('Invalid argument');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.remote = remote.remote;
|
||||||
|
|
||||||
|
if (res.indistinguishableFromLocal) {
|
||||||
|
res.remote = res.remote.split('://')[1];
|
||||||
|
}
|
||||||
|
if (remote.fragment) {
|
||||||
|
res.commit = remote.fragment.ref;
|
||||||
|
res.subDir = remote.fragment.subdir;
|
||||||
|
}
|
||||||
|
|
||||||
|
const repoSplitBySlash = res.remote.split('/');
|
||||||
|
res.shortName = repoSplitBySlash[repoSplitBySlash.length - 1].replace('.git', '');
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
44
src/types/buildkit/git.ts
Normal file
44
src/types/buildkit/git.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2024 actions-toolkit authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface GitURL {
|
||||||
|
scheme: string;
|
||||||
|
host: string;
|
||||||
|
path: string;
|
||||||
|
user?: URLUserInfo;
|
||||||
|
fragment?: GitURLFragment;
|
||||||
|
remote: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GitURLFragment {
|
||||||
|
ref: string;
|
||||||
|
subdir: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GitRef {
|
||||||
|
remote?: string;
|
||||||
|
shortName?: string;
|
||||||
|
commit?: string;
|
||||||
|
subDir?: string;
|
||||||
|
indistinguishableFromLocal?: boolean;
|
||||||
|
unencryptedTCP?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface URLUserInfo {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
passwordSet: boolean;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user