mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge pull request #3049 from Infisical/daniel/vercel-custom-envs
feat(integrations/vercel): custom environments support
This commit is contained in:
@@ -1151,6 +1151,50 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/:integrationAuthId/vercel/custom-environments",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
schema: {
|
||||
querystring: z.object({
|
||||
teamId: z.string().trim()
|
||||
}),
|
||||
params: z.object({
|
||||
integrationAuthId: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
environments: z
|
||||
.object({
|
||||
appId: z.string(),
|
||||
customEnvironments: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
slug: z.string()
|
||||
})
|
||||
.array()
|
||||
})
|
||||
.array()
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
const environments = await server.services.integrationAuth.getVercelCustomEnvironments({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
id: req.params.integrationAuthId,
|
||||
teamId: req.query.teamId
|
||||
});
|
||||
|
||||
return { environments };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/:integrationAuthId/octopus-deploy/spaces",
|
||||
|
||||
@@ -132,16 +132,26 @@ const getAppsHeroku = async ({ accessToken }: { accessToken: string }) => {
|
||||
|
||||
/**
|
||||
* Return list of names of apps for Vercel integration
|
||||
* This is re-used for getting custom environments for Vercel
|
||||
*/
|
||||
const getAppsVercel = async ({ accessToken, teamId }: { teamId?: string | null; accessToken: string }) => {
|
||||
const apps: Array<{ name: string; appId: string }> = [];
|
||||
export const getAppsVercel = async ({ accessToken, teamId }: { teamId?: string | null; accessToken: string }) => {
|
||||
const apps: Array<{ name: string; appId: string; customEnvironments: Array<{ slug: string; id: string }> }> = [];
|
||||
|
||||
const limit = "20";
|
||||
let hasMorePages = true;
|
||||
let next: number | null = null;
|
||||
|
||||
interface Response {
|
||||
projects: { name: string; id: string }[];
|
||||
projects: {
|
||||
name: string;
|
||||
id: string;
|
||||
customEnvironments?: {
|
||||
id: string;
|
||||
type: string;
|
||||
description: string;
|
||||
slug: string;
|
||||
}[];
|
||||
}[];
|
||||
pagination: {
|
||||
count: number;
|
||||
next: number | null;
|
||||
@@ -173,7 +183,12 @@ const getAppsVercel = async ({ accessToken, teamId }: { teamId?: string | null;
|
||||
data.projects.forEach((a) => {
|
||||
apps.push({
|
||||
name: a.name,
|
||||
appId: a.id
|
||||
appId: a.id,
|
||||
customEnvironments:
|
||||
a.customEnvironments?.map((env) => ({
|
||||
slug: env.slug,
|
||||
id: env.id
|
||||
})) ?? []
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -25,11 +25,12 @@ import { TIntegrationDALFactory } from "../integration/integration-dal";
|
||||
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||
import { KmsDataKey } from "../kms/kms-types";
|
||||
import { TProjectBotServiceFactory } from "../project-bot/project-bot-service";
|
||||
import { getApps } from "./integration-app-list";
|
||||
import { getApps, getAppsVercel } from "./integration-app-list";
|
||||
import { TCircleCIContext } from "./integration-app-types";
|
||||
import { TIntegrationAuthDALFactory } from "./integration-auth-dal";
|
||||
import { IntegrationAuthMetadataSchema, TIntegrationAuthMetadata } from "./integration-auth-schema";
|
||||
import {
|
||||
GetVercelCustomEnvironmentsDTO,
|
||||
OctopusDeployScope,
|
||||
TBitbucketEnvironment,
|
||||
TBitbucketWorkspace,
|
||||
@@ -1825,6 +1826,41 @@ export const integrationAuthServiceFactory = ({
|
||||
return integrationAuthDAL.create(newIntegrationAuth);
|
||||
};
|
||||
|
||||
const getVercelCustomEnvironments = async ({
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
teamId,
|
||||
id
|
||||
}: GetVercelCustomEnvironmentsDTO) => {
|
||||
const integrationAuth = await integrationAuthDAL.findById(id);
|
||||
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
actorId,
|
||||
projectId: integrationAuth.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
||||
|
||||
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(integrationAuth.projectId);
|
||||
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
|
||||
|
||||
const vercelApps = await getAppsVercel({
|
||||
accessToken,
|
||||
teamId
|
||||
});
|
||||
|
||||
return vercelApps.map((app) => ({
|
||||
customEnvironments: app.customEnvironments,
|
||||
appId: app.appId
|
||||
}));
|
||||
};
|
||||
|
||||
const getOctopusDeploySpaces = async ({
|
||||
actorId,
|
||||
actor,
|
||||
@@ -1944,6 +1980,7 @@ export const integrationAuthServiceFactory = ({
|
||||
getIntegrationAccessToken,
|
||||
duplicateIntegrationAuth,
|
||||
getOctopusDeploySpaces,
|
||||
getOctopusDeployScopeValues
|
||||
getOctopusDeployScopeValues,
|
||||
getVercelCustomEnvironments
|
||||
};
|
||||
};
|
||||
|
||||
@@ -284,3 +284,8 @@ export type TOctopusDeployVariableSet = {
|
||||
Self: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type GetVercelCustomEnvironmentsDTO = {
|
||||
teamId: string;
|
||||
id: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
@@ -1450,9 +1450,13 @@ const syncSecretsVercel = async ({
|
||||
secrets: Record<string, { value: string; comment?: string } | null>;
|
||||
accessToken: string;
|
||||
}) => {
|
||||
const isCustomEnvironment = !["development", "preview", "production"].includes(
|
||||
integration.targetEnvironment as string
|
||||
);
|
||||
interface VercelSecret {
|
||||
id?: string;
|
||||
type: string;
|
||||
customEnvironmentIds?: string[];
|
||||
key: string;
|
||||
value: string;
|
||||
target: string[];
|
||||
@@ -1486,6 +1490,16 @@ const syncSecretsVercel = async ({
|
||||
}
|
||||
)
|
||||
).data.envs.filter((secret) => {
|
||||
if (isCustomEnvironment) {
|
||||
if (!secret.customEnvironmentIds?.includes(integration.targetEnvironment as string)) {
|
||||
// case: secret does not have the same custom environment
|
||||
return false;
|
||||
}
|
||||
|
||||
// no need to check for preview environment, as custom environments are not available in preview
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!secret.target.includes(integration.targetEnvironment as string)) {
|
||||
// case: secret does not have the same target environment
|
||||
return false;
|
||||
@@ -1583,7 +1597,13 @@ const syncSecretsVercel = async ({
|
||||
key,
|
||||
value: infisicalSecrets[key]?.value,
|
||||
type: "encrypted",
|
||||
target: [integration.targetEnvironment as string],
|
||||
...(isCustomEnvironment
|
||||
? {
|
||||
customEnvironmentIds: [integration.targetEnvironment as string]
|
||||
}
|
||||
: {
|
||||
target: [integration.targetEnvironment as string]
|
||||
}),
|
||||
...(integration.path
|
||||
? {
|
||||
gitBranch: integration.path
|
||||
@@ -1607,9 +1627,19 @@ const syncSecretsVercel = async ({
|
||||
key,
|
||||
value: infisicalSecrets[key]?.value,
|
||||
type: res[key].type,
|
||||
target: res[key].target.includes(integration.targetEnvironment as string)
|
||||
? [...res[key].target]
|
||||
: [...res[key].target, integration.targetEnvironment as string],
|
||||
|
||||
...(!isCustomEnvironment
|
||||
? {
|
||||
target: res[key].target.includes(integration.targetEnvironment as string)
|
||||
? [...res[key].target]
|
||||
: [...res[key].target, integration.targetEnvironment as string]
|
||||
}
|
||||
: {
|
||||
customEnvironmentIds: res[key].customEnvironmentIds?.includes(integration.targetEnvironment as string)
|
||||
? [...(res[key].customEnvironmentIds || [])]
|
||||
: [...(res[key]?.customEnvironmentIds || []), integration.targetEnvironment as string]
|
||||
}),
|
||||
|
||||
...(integration.path
|
||||
? {
|
||||
gitBranch: integration.path
|
||||
|
||||
@@ -16,5 +16,6 @@ export {
|
||||
useGetIntegrationAuthTeamCityBuildConfigs,
|
||||
useGetIntegrationAuthTeams,
|
||||
useGetIntegrationAuthVercelBranches,
|
||||
useGetIntegrationAuthVercelCustomEnvironments,
|
||||
useSaveIntegrationAccessToken
|
||||
} from "./queries";
|
||||
|
||||
@@ -21,7 +21,8 @@ import {
|
||||
Team,
|
||||
TeamCityBuildConfig,
|
||||
TGetIntegrationAuthOctopusDeployScopeValuesDTO,
|
||||
TOctopusDeployVariableSetScopeValues
|
||||
TOctopusDeployVariableSetScopeValues,
|
||||
VercelEnvironment
|
||||
} from "./types";
|
||||
|
||||
const integrationAuthKeys = {
|
||||
@@ -132,7 +133,9 @@ const integrationAuthKeys = {
|
||||
}: TGetIntegrationAuthOctopusDeployScopeValuesDTO) =>
|
||||
[{ integrationAuthId }, "getIntegrationAuthOctopusDeployScopeValues", params] as const,
|
||||
getIntegrationAuthCircleCIOrganizations: (integrationAuthId: string) =>
|
||||
[{ integrationAuthId }, "getIntegrationAuthCircleCIOrganizations"] as const
|
||||
[{ integrationAuthId }, "getIntegrationAuthCircleCIOrganizations"] as const,
|
||||
getIntegrationAuthVercelCustomEnv: (integrationAuthId: string, teamId: string) =>
|
||||
[{ integrationAuthId, teamId }, "integrationAuthVercelCustomEnv"] as const
|
||||
};
|
||||
|
||||
const fetchIntegrationAuthById = async (integrationAuthId: string) => {
|
||||
@@ -362,6 +365,29 @@ const fetchIntegrationAuthQoveryScopes = async ({
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const fetchIntegrationAuthVercelCustomEnvironments = async ({
|
||||
integrationAuthId,
|
||||
teamId
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
teamId: string;
|
||||
}) => {
|
||||
const {
|
||||
data: { environments }
|
||||
} = await apiRequest.get<{
|
||||
environments: {
|
||||
appId: string;
|
||||
customEnvironments: VercelEnvironment[];
|
||||
}[];
|
||||
}>(`/api/v1/integration-auth/${integrationAuthId}/vercel/custom-environments`, {
|
||||
params: {
|
||||
teamId
|
||||
}
|
||||
});
|
||||
|
||||
return environments;
|
||||
};
|
||||
|
||||
const fetchIntegrationAuthHerokuPipelines = async ({
|
||||
integrationAuthId
|
||||
}: {
|
||||
@@ -730,6 +756,24 @@ export const useGetIntegrationAuthQoveryScopes = ({
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetIntegrationAuthVercelCustomEnvironments = ({
|
||||
integrationAuthId,
|
||||
teamId
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
teamId: string;
|
||||
}) => {
|
||||
return useQuery({
|
||||
queryKey: integrationAuthKeys.getIntegrationAuthVercelCustomEnv(integrationAuthId, teamId),
|
||||
queryFn: () =>
|
||||
fetchIntegrationAuthVercelCustomEnvironments({
|
||||
integrationAuthId,
|
||||
teamId
|
||||
}),
|
||||
enabled: Boolean(teamId && integrationAuthId)
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetIntegrationAuthHerokuPipelines = ({
|
||||
integrationAuthId
|
||||
}: {
|
||||
|
||||
@@ -43,6 +43,11 @@ export type Environment = {
|
||||
environmentId: string;
|
||||
};
|
||||
|
||||
export type VercelEnvironment = {
|
||||
id: string;
|
||||
slug: string;
|
||||
};
|
||||
|
||||
export type ChecklyGroup = {
|
||||
name: string;
|
||||
groupId: number;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Helmet } from "react-helmet";
|
||||
import {
|
||||
faArrowUpRightFromSquare,
|
||||
@@ -25,7 +25,8 @@ import { useCreateIntegration } from "@app/hooks/api";
|
||||
import {
|
||||
useGetIntegrationAuthApps,
|
||||
useGetIntegrationAuthById,
|
||||
useGetIntegrationAuthVercelBranches
|
||||
useGetIntegrationAuthVercelBranches,
|
||||
useGetIntegrationAuthVercelCustomEnvironments
|
||||
} from "@app/hooks/api/integrationAuth";
|
||||
import { IntegrationSyncBehavior } from "@app/hooks/api/integrations/types";
|
||||
|
||||
@@ -75,6 +76,11 @@ export const VercelConfigurePage = () => {
|
||||
teamId: integrationAuth?.teamId as string
|
||||
});
|
||||
|
||||
const { data: customEnvironments } = useGetIntegrationAuthVercelCustomEnvironments({
|
||||
teamId: integrationAuth?.teamId as string,
|
||||
integrationAuthId: integrationAuthId as string
|
||||
});
|
||||
|
||||
const { data: branches } = useGetIntegrationAuthVercelBranches({
|
||||
integrationAuthId: integrationAuthId as string,
|
||||
appId: targetAppId
|
||||
@@ -135,6 +141,26 @@ export const VercelConfigurePage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const selectedVercelEnvironments = useMemo(() => {
|
||||
let selectedEnvironments = vercelEnvironments;
|
||||
|
||||
const environments = customEnvironments?.find(
|
||||
(e) => e.appId === targetAppId
|
||||
)?.customEnvironments;
|
||||
|
||||
if (environments && environments.length > 0) {
|
||||
selectedEnvironments = [
|
||||
...selectedEnvironments,
|
||||
...environments.map((env) => ({
|
||||
name: env.slug,
|
||||
slug: env.id
|
||||
}))
|
||||
];
|
||||
}
|
||||
|
||||
return selectedEnvironments;
|
||||
}, [targetAppId, customEnvironments]);
|
||||
|
||||
return integrationAuth &&
|
||||
selectedSourceEnvironment &&
|
||||
integrationAuthApps &&
|
||||
@@ -210,7 +236,13 @@ export const VercelConfigurePage = () => {
|
||||
>
|
||||
<Select
|
||||
value={targetAppId}
|
||||
onValueChange={(val) => setTargetAppId(val)}
|
||||
onValueChange={(val) => {
|
||||
if (vercelEnvironments.every((env) => env.slug !== targetEnvironment)) {
|
||||
setTargetEnvironment(vercelEnvironments[0].slug);
|
||||
}
|
||||
|
||||
setTargetAppId(val);
|
||||
}}
|
||||
className="w-full border border-mineshaft-500"
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
@@ -236,7 +268,7 @@ export const VercelConfigurePage = () => {
|
||||
onValueChange={(val) => setTargetEnvironment(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
>
|
||||
{vercelEnvironments.map((vercelEnvironment) => (
|
||||
{selectedVercelEnvironments.map((vercelEnvironment) => (
|
||||
<SelectItem
|
||||
value={vercelEnvironment.slug}
|
||||
key={`target-environment-${vercelEnvironment.slug}`}
|
||||
|
||||
Reference in New Issue
Block a user