Skip to content

Commit

Permalink
Merge pull request #3049 from Infisical/daniel/vercel-custom-envs
Browse files Browse the repository at this point in the history
feat(integrations/vercel): custom environments support
  • Loading branch information
DanielHougaard authored Jan 28, 2025
2 parents c3d515b + 27af943 commit 657aca5
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 16 deletions.
44 changes: 44 additions & 0 deletions backend/src/server/routes/v1/integration-auth-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
23 changes: 19 additions & 4 deletions backend/src/services/integration-auth/integration-app-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
})) ?? []
});
});

Expand Down
41 changes: 39 additions & 2 deletions backend/src/services/integration-auth/integration-auth-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -1944,6 +1980,7 @@ export const integrationAuthServiceFactory = ({
getIntegrationAccessToken,
duplicateIntegrationAuth,
getOctopusDeploySpaces,
getOctopusDeployScopeValues
getOctopusDeployScopeValues,
getVercelCustomEnvironments
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -284,3 +284,8 @@ export type TOctopusDeployVariableSet = {
Self: string;
};
};

export type GetVercelCustomEnvironmentsDTO = {
teamId: string;
id: string;
} & Omit<TProjectPermission, "projectId">;
38 changes: 34 additions & 4 deletions backend/src/services/integration-auth/integration-sync-secret.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions frontend/src/hooks/api/integrationAuth/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ export {
useGetIntegrationAuthTeamCityBuildConfigs,
useGetIntegrationAuthTeams,
useGetIntegrationAuthVercelBranches,
useGetIntegrationAuthVercelCustomEnvironments,
useSaveIntegrationAccessToken
} from "./queries";
48 changes: 46 additions & 2 deletions frontend/src/hooks/api/integrationAuth/queries.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import {
Team,
TeamCityBuildConfig,
TGetIntegrationAuthOctopusDeployScopeValuesDTO,
TOctopusDeployVariableSetScopeValues
TOctopusDeployVariableSetScopeValues,
VercelEnvironment
} from "./types";

const integrationAuthKeys = {
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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
}: {
Expand Down Expand Up @@ -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
}: {
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/hooks/api/integrationAuth/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ export type Environment = {
environmentId: string;
};

export type VercelEnvironment = {
id: string;
slug: string;
};

export type ChecklyGroup = {
name: string;
groupId: number;
Expand Down
Loading

0 comments on commit 657aca5

Please sign in to comment.