diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts
index 09d3847716..e3d62c42c4 100644
--- a/backend/src/config/index.ts
+++ b/backend/src/config/index.ts
@@ -16,6 +16,7 @@ const CLIENT_ID_VERCEL = process.env.CLIENT_ID_VERCEL!;
const CLIENT_ID_NETLIFY = process.env.CLIENT_ID_NETLIFY!;
const CLIENT_SECRET_VERCEL = process.env.CLIENT_SECRET_VERCEL!;
const CLIENT_SECRET_NETLIFY = process.env.CLIENT_SECRET_NETLIFY!;
+const CLIENT_SLUG_VERCEL= process.env.CLIENT_SLUG_VERCEL!;
const POSTHOG_HOST = process.env.POSTHOG_HOST! || 'https://app.posthog.com';
const POSTHOG_PROJECT_API_KEY =
process.env.POSTHOG_PROJECT_API_KEY! ||
@@ -56,6 +57,7 @@ export {
CLIENT_SECRET_HEROKU,
CLIENT_SECRET_VERCEL,
CLIENT_SECRET_NETLIFY,
+ CLIENT_SLUG_VERCEL,
POSTHOG_HOST,
POSTHOG_PROJECT_API_KEY,
PRIVATE_KEY,
diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts
index 3cef519cc2..e6545a1021 100644
--- a/backend/src/integrations/sync.ts
+++ b/backend/src/integrations/sync.ts
@@ -61,6 +61,8 @@ const syncSecrets = async ({
});
break;
}
+
+ // TODO: set integration to inactive if it was not synced correctly (send alert?)
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
@@ -101,8 +103,8 @@ const syncSecretsHeroku = async ({
});
await axios.patch(
- `${INTEGRATION_HEROKU_API_URL}/apps/${integration.app}/config-vars`,
- secrets,
+ `${INTEGRATION_HEROKU_API_URL}/apps/${integration.app}/config-vars`,
+ secrets,
{
headers: {
Accept: 'application/vnd.heroku+json; version=3',
@@ -174,9 +176,9 @@ const syncSecretsVercel = async ({
[secret.key]: secret
}), {});
- let updateSecrets: VercelSecret[] = [];
- let deleteSecrets: VercelSecret[] = [];
- let newSecrets: VercelSecret[] = [];
+ const updateSecrets: VercelSecret[] = [];
+ const deleteSecrets: VercelSecret[] = [];
+ const newSecrets: VercelSecret[] = [];
// Identify secrets to create
Object.keys(secrets).map((key) => {
@@ -286,6 +288,173 @@ const syncSecretsNetlify = async ({
secrets: any;
accessToken: string;
}) => {
+ // TODO: Netlify revision in progress
+ // try {
+ // const getParams = new URLSearchParams({
+ // context_name: integration.context,
+ // site_id: integration.siteId
+ // });
+
+ // const res = (await axios.get(
+ // `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env`,
+ // {
+ // params: getParams,
+ // headers: {
+ // Authorization: `Bearer ${accessToken}`
+ // }
+ // }
+ // ))
+ // .data
+ // .reduce((obj: any, secret: any) => ({
+ // ...obj,
+ // [secret.key]: secret
+ // }), {});
+
+ // res.forEach((r: any) => console.log(r));
+ // console.log('getParams', getParams);
+
+ // interface UpdateNetlifySecret {
+ // key: string;
+ // context: string;
+ // value: string;
+ // }
+
+ // interface DeleteNetlifySecret {
+ // key: string;
+ // }
+
+ // interface NewNetlifySecretValue {
+ // value: string;
+ // context: string;
+ // }
+
+ // interface NewNetlifySecret {
+ // key: string;
+ // values: NewNetlifySecretValue[];
+ // }
+
+ // let updateSecrets: UpdateNetlifySecret[] = [];
+ // let deleteSecrets: DeleteNetlifySecret[] = [];
+ // let newSecrets: NewNetlifySecret[] = [];
+
+ // interface NetlifyValue {
+ // id: string;
+ // value: string;
+ // context: string;
+ // role: string;
+ // }
+
+ // // Identify secrets to create - GOOD
+ // Object.keys(secrets).map((key) => {
+ // if (!(key in res)) {
+ // // case: secret does not exist in Netlify -> create secret
+ // newSecrets.push({
+ // key: key,
+ // values: [{
+ // value: secrets[key],
+ // context: integration.context
+ // }]
+ // });
+ // } else {
+ // // case: secret exists in Netlify
+
+ // const netlifyContextsSet = new Set (res[key].values.map((netlifyValue: NetlifyValue) => netlifyValue.context));
+
+ // // TODO: check context/env.
+ // res[key].values.forEach((netlifyValue: NetlifyValue) => {
+ // if (netlifyValue.context === integration.context) {
+ // // case: Netlify value context matches integration context
+ // // TODO: check if value has changed
+ // }
+ // });
+ // }
+ // });
+
+ // // Identify secrets to update and delete
+ // Object.keys(res).map((key) => {
+ // if (key in secrets) {
+ // // if (res[key] !== secrets[key]) {
+ // // // case: secret value has changed
+ // // updateSecrets.push({
+ // // key: key,
+ // // context: integration.context,
+ // // value: secrets[key]
+ // // });
+ // // }
+
+ // // TODO: modify check and record of updated secrets
+
+ // // case 1: new context added.
+ // } else {
+ // // case: secret has been deleted
+ // deleteSecrets.push({
+ // key
+ // });
+ // }
+ // });
+
+ // const syncParams = new URLSearchParams({
+ // site_id: integration.siteId
+ // });
+
+ // console.log('Netlify newSecrets', newSecrets);
+ // newSecrets.forEach(secret => console.log(secret.values));
+ // // Sync/push new secrets
+ // if (newSecrets.length > 0) {
+ // await axios.post(
+ // `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env`,
+ // newSecrets,
+ // {
+ // params: syncParams,
+ // headers: {
+ // Authorization: `Bearer ${accessToken}`
+ // }
+ // }
+ // );
+ // }
+
+ // console.log('Netlify updateSecrets', updateSecrets);
+ // // Sync/push updated secrets
+ // if (updateSecrets.length > 0) {
+
+ // updateSecrets.forEach(async (secret: UpdateNetlifySecret) => {
+ // await axios.patch(
+ // `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}`,
+ // {
+ // context: secret.context,
+ // value: secret.value
+ // },
+ // {
+ // params: syncParams,
+ // headers: {
+ // Authorization: `Bearer ${accessToken}`
+ // }
+ // }
+ // );
+ // });
+ // }
+
+ // console.log('Netlify deleteSecrets', deleteSecrets);
+ // // Delete secrets
+ // if (deleteSecrets.length > 0) {
+ // deleteSecrets.forEach(async (secret: DeleteNetlifySecret) => {
+ // await axios.delete(
+ // `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}`,
+ // {
+ // params: syncParams,
+ // headers: {
+ // Authorization: `Bearer ${accessToken}`
+ // }
+ // }
+ // );
+ // });
+ // }
+ // } catch (err) {
+ // Sentry.setUser(null);
+ // Sentry.captureException(err);
+ // throw new Error('Failed to sync secrets to Heroku');
+ // }
+
try {
const getParams = new URLSearchParams({
context_name: integration.context,
@@ -327,9 +496,9 @@ const syncSecretsNetlify = async ({
values: NewNetlifySecretValue[];
}
- let updateSecrets: UpdateNetlifySecret[] = [];
- let deleteSecrets: DeleteNetlifySecret[] = [];
- let newSecrets: NewNetlifySecret[] = [];
+ const updateSecrets: UpdateNetlifySecret[] = [];
+ const deleteSecrets: DeleteNetlifySecret[] = [];
+ const newSecrets: NewNetlifySecret[] = [];
// Identify secrets to create
Object.keys(secrets).map((key) => {
diff --git a/backend/src/variables/integration.ts b/backend/src/variables/integration.ts
index c02a2389fb..35c6323894 100644
--- a/backend/src/variables/integration.ts
+++ b/backend/src/variables/integration.ts
@@ -1,6 +1,7 @@
import {
CLIENT_ID_HEROKU,
- CLIENT_ID_NETLIFY
+ CLIENT_ID_NETLIFY,
+ CLIENT_SLUG_VERCEL
} from '../config';
// integrations
@@ -43,13 +44,14 @@ const INTEGRATION_OPTIONS = [
isAvailable: true,
type: 'vercel',
clientId: '',
+ clientSlug: CLIENT_SLUG_VERCEL,
docsLink: ''
},
{
name: 'Netlify',
slug: 'netlify',
image: 'Netlify',
- isAvailable: true,
+ isAvailable: false,
type: 'oauth2',
clientId: CLIENT_ID_NETLIFY,
docsLink: ''
diff --git a/frontend/components/basic/Listbox.tsx b/frontend/components/basic/Listbox.tsx
index 2ff0179dd6..e726b7f56c 100644
--- a/frontend/components/basic/Listbox.tsx
+++ b/frontend/components/basic/Listbox.tsx
@@ -11,7 +11,7 @@ import { Listbox, Transition } from "@headlessui/react";
interface ListBoxProps {
selected: string;
onChange: (arg: string) => void;
- data: string[];
+ data: string[] | null;
text?: string;
buttonAction?: () => void;
isFull?: boolean;
diff --git a/frontend/components/integrations/Integration.tsx b/frontend/components/integrations/Integration.tsx
index de0747409c..053269be40 100644
--- a/frontend/components/integrations/Integration.tsx
+++ b/frontend/components/integrations/Integration.tsx
@@ -14,9 +14,11 @@ import deleteIntegration from "../../pages/api/integrations/DeleteIntegration"
import getIntegrationApps from "../../pages/api/integrations/GetIntegrationApps";
import updateIntegration from "../../pages/api/integrations/updateIntegration"
import {
+ contextNetlifyMapping,
envMapping,
reverseContextNetlifyMapping,
- reverseEnvMapping} from "../../public/data/frequentConstants";
+ reverseEnvMapping,
+} from "../../public/data/frequentConstants";
interface Integration {
_id: string;
@@ -25,6 +27,7 @@ interface Integration {
integration: string;
integrationAuth: string;
isActive: boolean;
+ context: string;
}
interface IntegrationApp {
@@ -69,7 +72,7 @@ const Integration = ({
setIntegrationTarget("Development");
break;
case "netlify":
- setIntegrationContext("All");
+ setIntegrationContext(integration?.context ? contextNetlifyMapping[integration.context] : "Local development");
break;
default:
break;
@@ -93,7 +96,7 @@ const Integration = ({
"Production",
"Preview",
"Development"
- ] : []}
+ ] : null}
selected={"Production"}
onChange={setIntegrationTarget}
/>
@@ -107,12 +110,11 @@ const Integration = ({
@@ -138,7 +140,7 @@ const Integration = ({
"Staging",
"Testing",
"Production",
- ] : []}
+ ] : null}
selected={integrationEnvironment}
onChange={(environment) => {
setIntegrationEnvironment(environment);
@@ -166,7 +168,7 @@ const Integration = ({
APP
app.name) : []}
+ data={!integration.isActive ? apps.map((app) => app.name) : null}
selected={integrationApp}
onChange={(app) => {
setIntegrationApp(app);
@@ -190,7 +192,8 @@ const Integration = ({
onButtonPressed={async () => {
const siteApp = apps.find((app) => app.name === integrationApp); // obj or undefined
- const siteId = siteApp ? siteApp.siteId : null;
+ const siteId = siteApp?.siteId ? siteApp.siteId : null;
+
const result = await updateIntegration({
integrationId: integration._id,
environment: envMapping[integrationEnvironment],
@@ -200,6 +203,7 @@ const Integration = ({
context: integrationContext ? reverseContextNetlifyMapping[integrationContext] : null,
siteId
});
+
router.reload();
}}
color="mineshaft"
diff --git a/frontend/pages/integrations/[id].js b/frontend/pages/integrations/[id].js
index be0aff9213..a7b5f6b133 100644
--- a/frontend/pages/integrations/[id].js
+++ b/frontend/pages/integrations/[id].js
@@ -41,7 +41,7 @@ export default function Integrations() {
setCloudIntegrationOptions(
await getIntegrationOptions()
);
-
+
// get project integration authorizations
setIntegrationAuths(
await getWorkspaceAuthorizations({
@@ -134,7 +134,7 @@ export default function Integrations() {
window.location = `https://id.heroku.com/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=write-protected&state=${state}`;
break;
case 'Vercel':
- window.location = `https://vercel.com/integrations/infisical-dev/new?state=${state}`;
+ window.location = `https://vercel.com/integrations/${integrationOption.clientSlug}/new?state=${state}`;
break;
case 'Netlify':
window.location = `https://app.netlify.com/authorize?client_id=${integrationOption.clientId}&response_type=code&state=${state}&redirect_uri=${window.location.origin}/netlify`;
diff --git a/frontend/public/data/frequentConstants.ts b/frontend/public/data/frequentConstants.ts
index a447225d49..d2beee6d26 100644
--- a/frontend/public/data/frequentConstants.ts
+++ b/frontend/public/data/frequentConstants.ts
@@ -16,8 +16,14 @@ const reverseEnvMapping: Mapping = {
test: "Testing",
};
+const contextNetlifyMapping: Mapping = {
+ "dev": "Local development",
+ "branch-deploy": "Branch deploys",
+ "deploy-review": "Deploy Previews",
+ "production": "Production"
+}
+
const reverseContextNetlifyMapping: Mapping = {
- "All": "all",
"Local development": "dev",
"Branch deploys": "branch-deploy",
"Deploy Previews": "deploy-preview",
@@ -25,6 +31,7 @@ const reverseContextNetlifyMapping: Mapping = {
}
export {
+ contextNetlifyMapping,
envMapping,
reverseContextNetlifyMapping,
- reverseEnvMapping};
+ reverseEnvMapping}