Merge pull request #835 from Dokploy/560-self-hosted-gitlab

560 self hosted gitlab
This commit is contained in:
Mauricio Siu
2024-12-10 22:07:59 -06:00
committed by GitHub
10 changed files with 4312 additions and 13 deletions

View File

@@ -33,6 +33,9 @@ const Schema = z.object({
name: z.string().min(1, { name: z.string().min(1, {
message: "Name is required", message: "Name is required",
}), }),
gitlabUrl: z.string().min(1, {
message: "GitLab URL is required",
}),
applicationId: z.string().min(1, { applicationId: z.string().min(1, {
message: "Application ID is required", message: "Application ID is required",
}), }),
@@ -62,16 +65,22 @@ export const AddGitlabProvider = () => {
applicationSecret: "", applicationSecret: "",
groupName: "", groupName: "",
redirectUri: webhookUrl, redirectUri: webhookUrl,
name: "",
gitlabUrl: "https://gitlab.com",
}, },
resolver: zodResolver(Schema), resolver: zodResolver(Schema),
}); });
const gitlabUrl = form.watch("gitlabUrl");
useEffect(() => { useEffect(() => {
form.reset({ form.reset({
applicationId: "", applicationId: "",
applicationSecret: "", applicationSecret: "",
groupName: "", groupName: "",
redirectUri: webhookUrl, redirectUri: webhookUrl,
name: "",
gitlabUrl: "https://gitlab.com",
}); });
}, [form, isOpen]); }, [form, isOpen]);
@@ -83,6 +92,7 @@ export const AddGitlabProvider = () => {
authId: auth?.id || "", authId: auth?.id || "",
name: data.name || "", name: data.name || "",
redirectUri: data.redirectUri || "", redirectUri: data.redirectUri || "",
gitlabUrl: data.gitlabUrl || "https://gitlab.com",
}) })
.then(async () => { .then(async () => {
await utils.gitProvider.getAll.invalidate(); await utils.gitProvider.getAll.invalidate();
@@ -129,7 +139,7 @@ export const AddGitlabProvider = () => {
<li className="flex flex-row gap-2 items-center"> <li className="flex flex-row gap-2 items-center">
Go to your GitLab profile settings{" "} Go to your GitLab profile settings{" "}
<Link <Link
href="https://gitlab.com/-/profile/applications" href={`${gitlabUrl}/-/profile/applications`}
target="_blank" target="_blank"
> >
<ExternalLink className="w-fit text-primary size-4" /> <ExternalLink className="w-fit text-primary size-4" />
@@ -169,6 +179,20 @@ export const AddGitlabProvider = () => {
)} )}
/> />
<FormField
control={form.control}
name="gitlabUrl"
render={({ field }) => (
<FormItem>
<FormLabel>Gitlab URL</FormLabel>
<FormControl>
<Input placeholder="https://gitlab.com/" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField <FormField
control={form.control} control={form.control}
name="redirectUri" name="redirectUri"

View File

@@ -30,6 +30,9 @@ const Schema = z.object({
name: z.string().min(1, { name: z.string().min(1, {
message: "Name is required", message: "Name is required",
}), }),
gitlabUrl: z.string().url({
message: "Invalid Gitlab URL",
}),
groupName: z.string().optional(), groupName: z.string().optional(),
}); });
@@ -40,7 +43,7 @@ interface Props {
} }
export const EditGitlabProvider = ({ gitlabId }: Props) => { export const EditGitlabProvider = ({ gitlabId }: Props) => {
const { data: gitlab } = api.gitlab.one.useQuery( const { data: gitlab, refetch } = api.gitlab.one.useQuery(
{ {
gitlabId, gitlabId,
}, },
@@ -57,6 +60,7 @@ export const EditGitlabProvider = ({ gitlabId }: Props) => {
defaultValues: { defaultValues: {
groupName: "", groupName: "",
name: "", name: "",
gitlabUrl: "https://gitlab.com",
}, },
resolver: zodResolver(Schema), resolver: zodResolver(Schema),
}); });
@@ -67,6 +71,7 @@ export const EditGitlabProvider = ({ gitlabId }: Props) => {
form.reset({ form.reset({
groupName: gitlab?.groupName || "", groupName: gitlab?.groupName || "",
name: gitlab?.gitProvider.name || "", name: gitlab?.gitProvider.name || "",
gitlabUrl: gitlab?.gitlabUrl || "",
}); });
}, [form, isOpen]); }, [form, isOpen]);
@@ -76,11 +81,13 @@ export const EditGitlabProvider = ({ gitlabId }: Props) => {
gitProviderId: gitlab?.gitProviderId || "", gitProviderId: gitlab?.gitProviderId || "",
groupName: data.groupName || "", groupName: data.groupName || "",
name: data.name || "", name: data.name || "",
gitlabUrl: data.gitlabUrl || "",
}) })
.then(async () => { .then(async () => {
await utils.gitProvider.getAll.invalidate(); await utils.gitProvider.getAll.invalidate();
toast.success("Gitlab updated successfully"); toast.success("Gitlab updated successfully");
setIsOpen(false); setIsOpen(false);
refetch();
}) })
.catch(() => { .catch(() => {
toast.error("Error to update Gitlab"); toast.error("Error to update Gitlab");
@@ -126,6 +133,19 @@ export const EditGitlabProvider = ({ gitlabId }: Props) => {
</FormItem> </FormItem>
)} )}
/> />
<FormField
control={form.control}
name="gitlabUrl"
render={({ field }) => (
<FormItem>
<FormLabel>Gitlab Url</FormLabel>
<FormControl>
<Input placeholder="https://gitlab.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField <FormField
control={form.control} control={form.control}

View File

@@ -23,12 +23,16 @@ export const ShowGitProviders = () => {
const url = useUrl(); const url = useUrl();
const getGitlabUrl = (clientId: string, gitlabId: string) => { const getGitlabUrl = (
clientId: string,
gitlabId: string,
gitlabUrl: string,
) => {
const redirectUri = `${url}/api/providers/gitlab/callback?gitlabId=${gitlabId}`; const redirectUri = `${url}/api/providers/gitlab/callback?gitlabId=${gitlabId}`;
const scope = "api read_user read_repository"; const scope = "api read_user read_repository";
const authUrl = `https://gitlab.com/oauth/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=${encodeURIComponent(scope)}`; const authUrl = `${gitlabUrl}/oauth/authorize?client_id=${clientId}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&scope=${encodeURIComponent(scope)}`;
return authUrl; return authUrl;
}; };
@@ -142,6 +146,7 @@ export const ShowGitProviders = () => {
href={getGitlabUrl( href={getGitlabUrl(
gitProvider.gitlab?.applicationId || "", gitProvider.gitlab?.applicationId || "",
gitProvider.gitlab?.gitlabId || "", gitProvider.gitlab?.gitlabId || "",
gitProvider.gitlab?.gitlabUrl,
)} )}
target="_blank" target="_blank"
className={buttonVariants({ className={buttonVariants({

View File

@@ -0,0 +1 @@
ALTER TABLE "gitlab" ADD COLUMN "gitlabUrl" text DEFAULT 'https://gitlab.com' NOT NULL;

File diff suppressed because it is too large Load Diff

View File

@@ -351,6 +351,13 @@
"when": 1733628762978, "when": 1733628762978,
"tag": "0049_dark_leopardon", "tag": "0049_dark_leopardon",
"breakpoints": true "breakpoints": true
},
{
"idx": 50,
"version": "6",
"when": 1733889104203,
"tag": "0050_nappy_wrecker",
"breakpoints": true
} }
] ]
} }

View File

@@ -13,7 +13,7 @@ export default async function handler(
const gitlab = await findGitlabById(gitlabId as string); const gitlab = await findGitlabById(gitlabId as string);
const response = await fetch("https://gitlab.com/oauth/token", { const response = await fetch(`${gitlab.gitlabUrl}/oauth/token`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",

View File

@@ -142,6 +142,10 @@ export const gitlabRouter = createTRPCRouter({
name: input.name, name: input.name,
adminId: ctx.user.adminId, adminId: ctx.user.adminId,
}); });
await updateGitlab(input.gitlabId, {
...input,
});
} else { } else {
await updateGitlab(input.gitlabId, { await updateGitlab(input.gitlabId, {
...input, ...input,

View File

@@ -10,6 +10,7 @@ export const gitlab = pgTable("gitlab", {
.notNull() .notNull()
.primaryKey() .primaryKey()
.$defaultFn(() => nanoid()), .$defaultFn(() => nanoid()),
gitlabUrl: text("gitlabUrl").default("https://gitlab.com").notNull(),
applicationId: text("application_id"), applicationId: text("application_id"),
redirectUri: text("redirect_uri"), redirectUri: text("redirect_uri"),
secret: text("secret"), secret: text("secret"),
@@ -39,6 +40,7 @@ export const apiCreateGitlab = createSchema.extend({
redirectUri: z.string().optional(), redirectUri: z.string().optional(),
authId: z.string().min(1), authId: z.string().min(1),
name: z.string().min(1), name: z.string().min(1),
gitlabUrl: z.string().min(1),
}); });
export const apiFindOneGitlab = createSchema export const apiFindOneGitlab = createSchema
@@ -67,4 +69,5 @@ export const apiUpdateGitlab = createSchema.extend({
redirectUri: z.string().optional(), redirectUri: z.string().optional(),
name: z.string().min(1), name: z.string().min(1),
gitlabId: z.string().min(1), gitlabId: z.string().min(1),
gitlabUrl: z.string().min(1),
}); });

View File

@@ -26,7 +26,7 @@ export const refreshGitlabToken = async (gitlabProviderId: string) => {
return; return;
} }
const response = await fetch("https://gitlab.com/oauth/token", { const response = await fetch(`${gitlabProvider.gitlabUrl}/oauth/token`, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
@@ -122,7 +122,7 @@ export const cloneGitlabRepository = async (
const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH; const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
const outputPath = join(basePath, appName, "code"); const outputPath = join(basePath, appName, "code");
await recreateDirectory(outputPath); await recreateDirectory(outputPath);
const repoclone = `gitlab.com/${gitlabPathNamespace}.git`; const repoclone = `${gitlab?.gitlabUrl.replace(/^https?:\/\//, "")}/${gitlabPathNamespace}.git`;
const cloneUrl = `https://oauth2:${gitlab?.accessToken}@${repoclone}`; const cloneUrl = `https://oauth2:${gitlab?.accessToken}@${repoclone}`;
try { try {
@@ -218,7 +218,7 @@ export const getGitlabCloneCommand = async (
const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH; const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
const outputPath = join(basePath, appName, "code"); const outputPath = join(basePath, appName, "code");
await recreateDirectory(outputPath); await recreateDirectory(outputPath);
const repoclone = `gitlab.com/${gitlabPathNamespace}.git`; const repoclone = `${gitlab?.gitlabUrl.replace(/^https?:\/\//, "")}/${gitlabPathNamespace}.git`;
const cloneUrl = `https://oauth2:${gitlab?.accessToken}@${repoclone}`; const cloneUrl = `https://oauth2:${gitlab?.accessToken}@${repoclone}`;
const cloneCommand = ` const cloneCommand = `
@@ -244,7 +244,7 @@ export const getGitlabRepositories = async (gitlabId?: string) => {
const gitlabProvider = await findGitlabById(gitlabId); const gitlabProvider = await findGitlabById(gitlabId);
const response = await fetch( const response = await fetch(
`https://gitlab.com/api/v4/projects?membership=true&owned=true&page=${0}&per_page=${100}`, `${gitlabProvider.gitlabUrl}/api/v4/projects?membership=true&owned=true&page=${0}&per_page=${100}`,
{ {
headers: { headers: {
Authorization: `Bearer ${gitlabProvider.accessToken}`, Authorization: `Bearer ${gitlabProvider.accessToken}`,
@@ -304,7 +304,7 @@ export const getGitlabBranches = async (input: {
const gitlabProvider = await findGitlabById(input.gitlabId); const gitlabProvider = await findGitlabById(input.gitlabId);
const branchesResponse = await fetch( const branchesResponse = await fetch(
`https://gitlab.com/api/v4/projects/${input.id}/repository/branches`, `${gitlabProvider.gitlabUrl}/api/v4/projects/${input.id}/repository/branches`,
{ {
headers: { headers: {
Authorization: `Bearer ${gitlabProvider.accessToken}`, Authorization: `Bearer ${gitlabProvider.accessToken}`,
@@ -350,7 +350,9 @@ export const cloneRawGitlabRepository = async (entity: Compose) => {
const basePath = COMPOSE_PATH; const basePath = COMPOSE_PATH;
const outputPath = join(basePath, appName, "code"); const outputPath = join(basePath, appName, "code");
await recreateDirectory(outputPath); await recreateDirectory(outputPath);
const repoclone = `gitlab.com/${gitlabPathNamespace}.git`; const gitlabUrl = gitlabProvider.gitlabUrl;
// What happen with oauth in self hosted instances?
const repoclone = `${gitlabUrl.replace(/^https?:\/\//, "")}/${gitlabPathNamespace}.git`;
const cloneUrl = `https://oauth2:${gitlabProvider?.accessToken}@${repoclone}`; const cloneUrl = `https://oauth2:${gitlabProvider?.accessToken}@${repoclone}`;
try { try {
@@ -390,7 +392,7 @@ export const cloneRawGitlabRepositoryRemote = async (compose: Compose) => {
await refreshGitlabToken(gitlabId); await refreshGitlabToken(gitlabId);
const basePath = COMPOSE_PATH; const basePath = COMPOSE_PATH;
const outputPath = join(basePath, appName, "code"); const outputPath = join(basePath, appName, "code");
const repoclone = `gitlab.com/${gitlabPathNamespace}.git`; const repoclone = `${gitlabProvider.gitlabUrl.replace(/^https?:\/\//, "")}/${gitlabPathNamespace}.git`;
const cloneUrl = `https://oauth2:${gitlabProvider?.accessToken}@${repoclone}`; const cloneUrl = `https://oauth2:${gitlabProvider?.accessToken}@${repoclone}`;
try { try {
const command = ` const command = `
@@ -417,7 +419,7 @@ export const testGitlabConnection = async (
const gitlabProvider = await findGitlabById(gitlabId); const gitlabProvider = await findGitlabById(gitlabId);
const response = await fetch( const response = await fetch(
`https://gitlab.com/api/v4/projects?membership=true&owned=true&page=${0}&per_page=${100}`, `${gitlabProvider.gitlabUrl}/api/v4/projects?membership=true&owned=true&page=${0}&per_page=${100}`,
{ {
headers: { headers: {
Authorization: `Bearer ${gitlabProvider.accessToken}`, Authorization: `Bearer ${gitlabProvider.accessToken}`,