mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge pull request #2895 from Infisical/daniel/vercel-integration-bug
fix(vercel-integration): vercel integration initial sync behavior
This commit is contained in:
@@ -1397,14 +1397,24 @@ const syncSecretsHeroku = async ({
|
||||
* Sync/push [secrets] to Vercel project named [integration.app]
|
||||
*/
|
||||
const syncSecretsVercel = async ({
|
||||
createManySecretsRawFn,
|
||||
integration,
|
||||
integrationAuth,
|
||||
secrets,
|
||||
secrets: infisicalSecrets,
|
||||
accessToken
|
||||
}: {
|
||||
integration: TIntegrations;
|
||||
createManySecretsRawFn: (params: TCreateManySecretsRawFn) => Promise<Array<{ id: string }>>;
|
||||
integration: TIntegrations & {
|
||||
projectId: string;
|
||||
environment: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
secretPath: string;
|
||||
};
|
||||
integrationAuth: TIntegrationAuths;
|
||||
secrets: Record<string, { value: string; comment?: string }>;
|
||||
secrets: Record<string, { value: string; comment?: string } | null>;
|
||||
accessToken: string;
|
||||
}) => {
|
||||
interface VercelSecret {
|
||||
@@ -1477,80 +1487,119 @@ const syncSecretsVercel = async ({
|
||||
}
|
||||
}
|
||||
|
||||
const updateSecrets: VercelSecret[] = [];
|
||||
const deleteSecrets: VercelSecret[] = [];
|
||||
const newSecrets: VercelSecret[] = [];
|
||||
const metadata = IntegrationMetadataSchema.parse(integration.metadata);
|
||||
|
||||
// Identify secrets to create
|
||||
Object.keys(secrets).forEach((key) => {
|
||||
if (!(key in res)) {
|
||||
// case: secret has been created
|
||||
newSecrets.push({
|
||||
key,
|
||||
value: secrets[key].value,
|
||||
type: "encrypted",
|
||||
target: [integration.targetEnvironment as string],
|
||||
...(integration.path
|
||||
? {
|
||||
gitBranch: integration.path
|
||||
}
|
||||
: {})
|
||||
});
|
||||
// Default to overwrite target for old integrations that doesn't have a initial sync behavior set.
|
||||
if (!metadata.initialSyncBehavior) {
|
||||
metadata.initialSyncBehavior = IntegrationInitialSyncBehavior.OVERWRITE_TARGET;
|
||||
}
|
||||
|
||||
const secretsToAddToInfisical: { [key: string]: VercelSecret } = {};
|
||||
|
||||
Object.keys(res).forEach((vercelKey) => {
|
||||
if (!integration.lastUsed) {
|
||||
// first time using integration
|
||||
// -> apply initial sync behavior
|
||||
switch (metadata.initialSyncBehavior) {
|
||||
// Override all the secrets in Vercel
|
||||
case IntegrationInitialSyncBehavior.OVERWRITE_TARGET: {
|
||||
if (!(vercelKey in infisicalSecrets)) infisicalSecrets[vercelKey] = null;
|
||||
break;
|
||||
}
|
||||
case IntegrationInitialSyncBehavior.PREFER_SOURCE: {
|
||||
// if the vercel secret is not in infisical, we need to add it to infisical
|
||||
if (!(vercelKey in infisicalSecrets)) {
|
||||
infisicalSecrets[vercelKey] = {
|
||||
value: res[vercelKey].value
|
||||
};
|
||||
secretsToAddToInfisical[vercelKey] = res[vercelKey];
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Invalid initial sync behavior: ${metadata.initialSyncBehavior}`);
|
||||
}
|
||||
}
|
||||
} else if (!(vercelKey in infisicalSecrets)) {
|
||||
infisicalSecrets[vercelKey] = null;
|
||||
}
|
||||
});
|
||||
|
||||
// Identify secrets to update and delete
|
||||
Object.keys(res).forEach((key) => {
|
||||
if (key in secrets) {
|
||||
if (res[key].value !== secrets[key].value) {
|
||||
// case: secret value has changed
|
||||
updateSecrets.push({
|
||||
id: res[key].id,
|
||||
key,
|
||||
value: secrets[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],
|
||||
...(integration.path
|
||||
? {
|
||||
gitBranch: integration.path
|
||||
}
|
||||
: {})
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// case: secret has been deleted
|
||||
deleteSecrets.push({
|
||||
id: res[key].id,
|
||||
key,
|
||||
value: res[key].value,
|
||||
type: "encrypted", // value doesn't matter
|
||||
target: [integration.targetEnvironment as string],
|
||||
...(integration.path
|
||||
? {
|
||||
gitBranch: integration.path
|
||||
}
|
||||
: {})
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Sync/push new secrets
|
||||
if (newSecrets.length > 0) {
|
||||
await request.post(`${IntegrationUrls.VERCEL_API_URL}/v10/projects/${integration.app}/env`, newSecrets, {
|
||||
params,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
if (Object.keys(secretsToAddToInfisical).length) {
|
||||
await createManySecretsRawFn({
|
||||
projectId: integration.projectId,
|
||||
environment: integration.environment.slug,
|
||||
path: integration.secretPath,
|
||||
secrets: Object.keys(secretsToAddToInfisical).map((key) => ({
|
||||
secretName: key,
|
||||
secretValue: secretsToAddToInfisical[key].value,
|
||||
type: SecretType.Shared,
|
||||
secretComment: ""
|
||||
}))
|
||||
});
|
||||
}
|
||||
|
||||
for await (const secret of updateSecrets) {
|
||||
if (secret.type !== "sensitive") {
|
||||
const { id, ...updatedSecret } = secret;
|
||||
await request.patch(`${IntegrationUrls.VERCEL_API_URL}/v9/projects/${integration.app}/env/${id}`, updatedSecret, {
|
||||
// update and create logic
|
||||
for await (const key of Object.keys(infisicalSecrets)) {
|
||||
if (!(key in res) || infisicalSecrets[key]?.value !== res[key].value) {
|
||||
// if the key is not in the vercel res, we need to create it
|
||||
if (!(key in res)) {
|
||||
await request.post(
|
||||
`${IntegrationUrls.VERCEL_API_URL}/v10/projects/${integration.app}/env`,
|
||||
{
|
||||
key,
|
||||
value: infisicalSecrets[key]?.value,
|
||||
type: "encrypted",
|
||||
target: [integration.targetEnvironment as string],
|
||||
...(integration.path
|
||||
? {
|
||||
gitBranch: integration.path
|
||||
}
|
||||
: {})
|
||||
},
|
||||
{
|
||||
params,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Else if the key already exists and its not sensitive, we need to update it
|
||||
} else if (res[key].type !== "sensitive") {
|
||||
await request.patch(
|
||||
`${IntegrationUrls.VERCEL_API_URL}/v9/projects/${integration.app}/env/${res[key].id}`,
|
||||
{
|
||||
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],
|
||||
...(integration.path
|
||||
? {
|
||||
gitBranch: integration.path
|
||||
}
|
||||
: {})
|
||||
},
|
||||
{
|
||||
params,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// delete logic
|
||||
for await (const key of Object.keys(res)) {
|
||||
if (infisicalSecrets[key] === null) {
|
||||
// case: delete secret
|
||||
await request.delete(`${IntegrationUrls.VERCEL_API_URL}/v9/projects/${integration.app}/env/${res[key].id}`, {
|
||||
params,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
@@ -1559,16 +1608,6 @@ const syncSecretsVercel = async ({
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for await (const secret of deleteSecrets) {
|
||||
await request.delete(`${IntegrationUrls.VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`, {
|
||||
params,
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -4471,7 +4510,8 @@ export const syncIntegrationSecrets = async ({
|
||||
integration,
|
||||
integrationAuth,
|
||||
secrets,
|
||||
accessToken
|
||||
accessToken,
|
||||
createManySecretsRawFn
|
||||
});
|
||||
break;
|
||||
case Integrations.NETLIFY:
|
||||
|
||||
@@ -13,6 +13,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import queryString from "query-string";
|
||||
|
||||
import { useCreateIntegration } from "@app/hooks/api";
|
||||
import { IntegrationSyncBehavior } from "@app/hooks/api/integrations/types";
|
||||
|
||||
import {
|
||||
Button,
|
||||
@@ -36,12 +37,26 @@ const vercelEnvironments = [
|
||||
{ name: "Production", slug: "production" }
|
||||
];
|
||||
|
||||
const initialSyncBehaviors = [
|
||||
{
|
||||
label: "No Import - Overwrite all values in Vercel",
|
||||
value: IntegrationSyncBehavior.OVERWRITE_TARGET
|
||||
},
|
||||
{
|
||||
label: "Import - Prefer values from Infisical",
|
||||
value: IntegrationSyncBehavior.PREFER_SOURCE
|
||||
}
|
||||
];
|
||||
|
||||
export default function VercelCreateIntegrationPage() {
|
||||
const router = useRouter();
|
||||
const { mutateAsync } = useCreateIntegration();
|
||||
|
||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
|
||||
const [secretPath, setSecretPath] = useState("/");
|
||||
const [initialSyncBehavior, setInitialSyncBehavior] = useState<IntegrationSyncBehavior>(
|
||||
IntegrationSyncBehavior.PREFER_SOURCE
|
||||
);
|
||||
const [targetAppId, setTargetAppId] = useState("");
|
||||
const [targetEnvironment, setTargetEnvironment] = useState("");
|
||||
const [targetBranch, setTargetBranch] = useState("");
|
||||
@@ -104,7 +119,10 @@ export default function VercelCreateIntegrationPage() {
|
||||
sourceEnvironment: selectedSourceEnvironment,
|
||||
targetEnvironment,
|
||||
path,
|
||||
secretPath
|
||||
secretPath,
|
||||
metadata: {
|
||||
initialSyncBehavior
|
||||
}
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
@@ -231,6 +249,21 @@ export default function VercelCreateIntegrationPage() {
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
|
||||
<FormControl label="Initial Sync Behavior" className="px-6">
|
||||
<Select
|
||||
value={initialSyncBehavior}
|
||||
onValueChange={(val) => setInitialSyncBehavior(val as IntegrationSyncBehavior)}
|
||||
className="w-full border border-mineshaft-500 text-sm"
|
||||
>
|
||||
{initialSyncBehaviors.map((syncBehavior) => (
|
||||
<SelectItem value={syncBehavior.value} key={`sync-behavior-${syncBehavior.value}`}>
|
||||
{syncBehavior.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
|
||||
Reference in New Issue
Block a user