mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 02:51:51 +00:00
General improvements to Vercel Integration
This commit is contained in:
@@ -1694,6 +1694,9 @@ export const AppConnections = {
|
||||
sslEnabled: "Whether or not to use SSL when connecting to the database.",
|
||||
sslRejectUnauthorized: "Whether or not to reject unauthorized SSL certificates.",
|
||||
sslCertificate: "The SSL certificate to use for connection."
|
||||
},
|
||||
VERCEL: {
|
||||
apiToken: "The API token used to authenticate with Vercel."
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1814,7 +1817,8 @@ export const SecretSyncs = {
|
||||
app: "The ID of the Vercel app to sync secrets to.",
|
||||
appName: "The name of the Vercel app to sync secrets to.",
|
||||
env: "The ID of the Vercel environment to sync secrets to.",
|
||||
branch: "The branch to sync preview secrets to."
|
||||
branch: "The branch to sync preview secrets to.",
|
||||
teamId: "The ID of the Vercel team to sync secrets to."
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -45,11 +45,11 @@ export const registerVercelConnectionRouter = async (server: FastifyZodProvider)
|
||||
name: z.string(),
|
||||
envs: z
|
||||
.object({
|
||||
key: z.string(),
|
||||
value: z.string(),
|
||||
id: z.string(),
|
||||
slug: z.string(),
|
||||
type: z.string(),
|
||||
target: z.array(z.string()).optional(),
|
||||
gitBranch: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
createdAt: z.number().optional(),
|
||||
updatedAt: z.number().optional()
|
||||
})
|
||||
|
||||
@@ -136,7 +136,6 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
|
||||
case DatabricksConnectionMethod.ServicePrincipal:
|
||||
return "Service Principal";
|
||||
case HumanitecConnectionMethod.ApiToken:
|
||||
return "API Token";
|
||||
case VercelConnectionMethod.ApiToken:
|
||||
return "API Token";
|
||||
case PostgresConnectionMethod.UsernameAndPassword:
|
||||
|
||||
@@ -133,29 +133,41 @@ async function fetchOrgProjects(orgId: string, apiToken: string): Promise<Vercel
|
||||
);
|
||||
}
|
||||
|
||||
async function fetchProjectEnvironments(projectId: string, apiToken: string): Promise<VercelEnvironment[]> {
|
||||
return fetchAllPages<VercelEnvironment>(
|
||||
`${IntegrationUrls.VERCEL_API_URL}/v9/projects/${projectId}/custom-environments`,
|
||||
apiToken,
|
||||
{},
|
||||
"environments"
|
||||
);
|
||||
async function fetchProjectEnvironments(
|
||||
projectId: string,
|
||||
teamId: string,
|
||||
apiToken: string
|
||||
): Promise<VercelEnvironment[]> {
|
||||
try {
|
||||
return await fetchAllPages<VercelEnvironment>(
|
||||
`${IntegrationUrls.VERCEL_API_URL}/v9/projects/${projectId}/custom-environments?teamId=${teamId}`,
|
||||
apiToken,
|
||||
{},
|
||||
"environments"
|
||||
);
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchPreviewBranches(projectId: string, apiToken: string): Promise<string[]> {
|
||||
const { data } = await request.get<TVercelBranches[]>(
|
||||
`${IntegrationUrls.VERCEL_API_URL}/v1/integrations/git-branches`,
|
||||
{
|
||||
params: {
|
||||
projectId
|
||||
},
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
try {
|
||||
const { data } = await request.get<TVercelBranches[]>(
|
||||
`${IntegrationUrls.VERCEL_API_URL}/v1/integrations/git-branches`,
|
||||
{
|
||||
params: {
|
||||
projectId
|
||||
},
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
return data.filter((b) => b.ref !== "main").map((b) => b.ref);
|
||||
);
|
||||
return data.filter((b) => b.ref !== "main").map((b) => b.ref);
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
type VercelTeam = {
|
||||
@@ -203,7 +215,7 @@ export const listProjects = async (appConnection: TVercelConnectionInput): Promi
|
||||
const enhancedProjectsPromises = projects.map(async (project) => {
|
||||
try {
|
||||
const [environments, previewBranches] = await Promise.all([
|
||||
fetchProjectEnvironments(project.id, apiToken),
|
||||
fetchProjectEnvironments(project.name, org.id, apiToken),
|
||||
fetchPreviewBranches(project.id, apiToken)
|
||||
]);
|
||||
|
||||
@@ -251,9 +263,9 @@ export const getProjectEnvironmentVariables = (project: VercelApp): Record<strin
|
||||
if (!project.envs) return envVars;
|
||||
|
||||
project.envs.forEach((env) => {
|
||||
if (env.value && env.type !== "gitBranch") {
|
||||
const { key, value } = env;
|
||||
envVars[key] = value;
|
||||
if (env.slug && env.type !== "gitBranch") {
|
||||
const { id, slug } = env;
|
||||
envVars[id] = slug;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@ export type VercelTeam = {
|
||||
};
|
||||
|
||||
export type VercelEnvironment = {
|
||||
key: string;
|
||||
value: string;
|
||||
id: string;
|
||||
slug: string;
|
||||
type: string;
|
||||
target?: string[];
|
||||
gitBranch?: string;
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { request } from "@app/lib/config/request";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
||||
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
|
||||
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
import { VercelEnvironmentType } from "./vercel-sync-enums";
|
||||
import { TVercelSyncWithCredentials, VercelApiSecret } from "./vercel-sync-types";
|
||||
import { DefaultVercelEnvType, TVercelSyncWithCredentials, VercelApiSecret } from "./vercel-sync-types";
|
||||
|
||||
function isVercelDefaultEnvType(value: string): value is DefaultVercelEnvType {
|
||||
return Object.values(VercelEnvironmentType).map(String).includes(value);
|
||||
}
|
||||
|
||||
const getVercelSecrets = async (secretSync: TVercelSyncWithCredentials) => {
|
||||
const {
|
||||
@@ -22,7 +25,7 @@ const getVercelSecrets = async (secretSync: TVercelSyncWithCredentials) => {
|
||||
};
|
||||
|
||||
const { data } = await request.get<{ envs: VercelApiSecret[] }>(
|
||||
`${IntegrationUrls.VERCEL_API_URL}/v9/projects/${destinationConfig.app}/env`,
|
||||
`${IntegrationUrls.VERCEL_API_URL}/v9/projects/${destinationConfig.app}/env?teamId=${destinationConfig.teamId}`,
|
||||
{
|
||||
params,
|
||||
headers: {
|
||||
@@ -33,7 +36,12 @@ const getVercelSecrets = async (secretSync: TVercelSyncWithCredentials) => {
|
||||
);
|
||||
|
||||
const filteredSecrets = data.envs.filter((secret) => {
|
||||
// For environment-specific filtering
|
||||
if (!isVercelDefaultEnvType(destinationConfig.env)) {
|
||||
if (secret.customEnvironmentIds?.includes(destinationConfig.env)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (secret.target.includes(destinationConfig.env)) {
|
||||
// If it's preview environment with a branch specified
|
||||
if (
|
||||
@@ -54,7 +62,7 @@ const getVercelSecrets = async (secretSync: TVercelSyncWithCredentials) => {
|
||||
filteredSecrets.map(async (secret) => {
|
||||
if (secret.type === "encrypted") {
|
||||
const { data: decryptedSecret } = await request.get(
|
||||
`${IntegrationUrls.VERCEL_API_URL}/v9/projects/${destinationConfig.app}/env/${secret.id}`,
|
||||
`${IntegrationUrls.VERCEL_API_URL}/v9/projects/${destinationConfig.app}/env/${secret.id}?teamId=${destinationConfig.teamId}`,
|
||||
{
|
||||
params,
|
||||
headers: {
|
||||
@@ -82,7 +90,7 @@ const deleteSecret = async (secretSync: TVercelSyncWithCredentials, vercelSecret
|
||||
|
||||
try {
|
||||
await request.delete(
|
||||
`${IntegrationUrls.VERCEL_API_URL}/v9/projects/${destinationConfig.app}/env/${vercelSecret.id}`,
|
||||
`${IntegrationUrls.VERCEL_API_URL}/v9/projects/${destinationConfig.app}/env/${vercelSecret.id}?teamId=${destinationConfig.teamId}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
@@ -108,12 +116,13 @@ const createSecret = async (secretSync: TVercelSyncWithCredentials, secretMap: T
|
||||
} = secretSync;
|
||||
|
||||
await request.post(
|
||||
`${IntegrationUrls.VERCEL_API_URL}/v10/projects/${destinationConfig.app}/env`,
|
||||
`${IntegrationUrls.VERCEL_API_URL}/v10/projects/${destinationConfig.app}/env?teamId=${destinationConfig.teamId}`,
|
||||
{
|
||||
key,
|
||||
value: secretMap[key].value,
|
||||
type: "encrypted",
|
||||
target: [destinationConfig.env],
|
||||
target: isVercelDefaultEnvType(destinationConfig.env) ? [destinationConfig.env] : [],
|
||||
customEnvironmentIds: !isVercelDefaultEnvType(destinationConfig.env) ? [destinationConfig.env] : [],
|
||||
...(destinationConfig.env === VercelEnvironmentType.Preview && destinationConfig.branch
|
||||
? { gitBranch: destinationConfig.branch }
|
||||
: {})
|
||||
@@ -146,31 +155,37 @@ const updateSecret = async (
|
||||
}
|
||||
} = secretSync;
|
||||
|
||||
// Only update if not sensitive type
|
||||
if (vercelSecret.type !== "sensitive") {
|
||||
await request.patch(
|
||||
`${IntegrationUrls.VERCEL_API_URL}/v9/projects/${destinationConfig.app}/env/${vercelSecret.id}`,
|
||||
{
|
||||
key: vercelSecret.key,
|
||||
value: secretMap[vercelSecret.key].value,
|
||||
type: vercelSecret.type,
|
||||
target: vercelSecret.target.includes(destinationConfig.env)
|
||||
? [...vercelSecret.target]
|
||||
: [...vercelSecret.target, destinationConfig.env],
|
||||
...(destinationConfig.env === VercelEnvironmentType.Preview && destinationConfig.branch
|
||||
? { gitBranch: destinationConfig.branch }
|
||||
: {})
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
logger.info(`Vercel secret ${vercelSecret.key} is of type 'sensitive' and cannot be updated through the API`);
|
||||
let target = [...vercelSecret.target];
|
||||
if (isVercelDefaultEnvType(destinationConfig.env) && !vercelSecret.target.includes(destinationConfig.env)) {
|
||||
target = [...target, destinationConfig.env];
|
||||
}
|
||||
let customEnvironmentIds = [...(vercelSecret.customEnvironmentIds || [])];
|
||||
if (
|
||||
!isVercelDefaultEnvType(destinationConfig.env) &&
|
||||
!vercelSecret.customEnvironmentIds?.includes(destinationConfig.env)
|
||||
) {
|
||||
customEnvironmentIds = [...customEnvironmentIds, destinationConfig.env];
|
||||
}
|
||||
|
||||
await request.patch(
|
||||
`${IntegrationUrls.VERCEL_API_URL}/v9/projects/${destinationConfig.app}/env/${vercelSecret.id}?teamId=${destinationConfig.teamId}`,
|
||||
{
|
||||
key: vercelSecret.key,
|
||||
value: secretMap[vercelSecret.key].value,
|
||||
type: vercelSecret.type,
|
||||
target,
|
||||
customEnvironmentIds,
|
||||
...(destinationConfig.env === VercelEnvironmentType.Preview && destinationConfig.branch
|
||||
? { gitBranch: destinationConfig.branch }
|
||||
: {})
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
throw new SecretSyncError({
|
||||
error,
|
||||
|
||||
@@ -15,10 +15,9 @@ import { VercelEnvironmentType } from "./vercel-sync-enums";
|
||||
const VercelSyncDestinationConfigSchema = z.object({
|
||||
app: z.string().min(1, "App ID is required").describe(SecretSyncs.DESTINATION_CONFIG.VERCEL.app),
|
||||
appName: z.string().min(1, "App Name is required").describe(SecretSyncs.DESTINATION_CONFIG.VERCEL.appName),
|
||||
env: z
|
||||
.enum([VercelEnvironmentType.Development, VercelEnvironmentType.Preview, VercelEnvironmentType.Production])
|
||||
.describe(SecretSyncs.DESTINATION_CONFIG.VERCEL.env),
|
||||
branch: z.string().optional().describe(SecretSyncs.DESTINATION_CONFIG.VERCEL.branch)
|
||||
env: z.nativeEnum(VercelEnvironmentType).or(z.string()).describe(SecretSyncs.DESTINATION_CONFIG.VERCEL.env),
|
||||
branch: z.string().optional().describe(SecretSyncs.DESTINATION_CONFIG.VERCEL.branch),
|
||||
teamId: z.string().describe(SecretSyncs.DESTINATION_CONFIG.VERCEL.teamId)
|
||||
});
|
||||
|
||||
const VercelSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };
|
||||
|
||||
@@ -2,6 +2,7 @@ import z from "zod";
|
||||
|
||||
import { TVercelConnection } from "@app/services/app-connection/vercel";
|
||||
|
||||
import { VercelEnvironmentType } from "./vercel-sync-enums";
|
||||
import { CreateVercelSyncSchema, VercelSyncListItemSchema, VercelSyncSchema } from "./vercel-sync-schemas";
|
||||
|
||||
export type TVercelSyncListItem = z.infer<typeof VercelSyncListItemSchema>;
|
||||
@@ -28,9 +29,12 @@ export interface VercelApiSecret {
|
||||
value: string;
|
||||
type: string;
|
||||
target: string[];
|
||||
customEnvironmentIds?: string[];
|
||||
gitBranch?: string;
|
||||
createdAt?: number;
|
||||
updatedAt?: number;
|
||||
configurationId?: string;
|
||||
system?: boolean;
|
||||
}
|
||||
|
||||
export type DefaultVercelEnvType = (typeof VercelEnvironmentType)[keyof typeof VercelEnvironmentType];
|
||||
|
||||
Reference in New Issue
Block a user