From 5971480ca929d8c79beae1938b902f3529460715 Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako Date: Sun, 8 Oct 2023 13:53:54 -0700 Subject: [PATCH 1/2] allow multiple simultaneous integrations with checkly and github --- backend/src/integrations/sync.ts | 40 ++++- .../syncSecretsToThirdPartyServices.ts | 3 +- .../src/pages/integrations/github/create.tsx | 143 ++++++++++++------ .../IntegrationsSection.tsx | 2 +- 4 files changed, 132 insertions(+), 56 deletions(-) diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts index c82cc5b4a..fe5885c9e 100644 --- a/backend/src/integrations/sync.ts +++ b/backend/src/integrations/sync.ts @@ -87,13 +87,15 @@ const syncSecrets = async ({ integrationAuth, secrets, accessId, - accessToken + accessToken, + appendices }: { integration: IIntegration; integrationAuth: IIntegrationAuth; secrets: Record; accessId: string | null; accessToken: string; + appendices?: { prefix: string, suffix: string }; }) => { switch (integration.integration) { case INTEGRATION_GCP_SECRET_MANAGER: @@ -153,7 +155,8 @@ const syncSecrets = async ({ await syncSecretsGitHub({ integration, secrets, - accessToken + accessToken, + appendices }); break; case INTEGRATION_GITLAB: @@ -218,7 +221,8 @@ const syncSecrets = async ({ await syncSecretsCheckly({ integration, secrets, - accessToken + accessToken, + appendices }); break; case INTEGRATION_QOVERY: @@ -1342,11 +1346,13 @@ const syncSecretsNetlify = async ({ const syncSecretsGitHub = async ({ integration, secrets, - accessToken + accessToken, + appendices }: { integration: IIntegration; secrets: Record; accessToken: string; + appendices?: { prefix: string, suffix: string }; }) => { interface GitHubRepoKey { key_id: string; @@ -1376,7 +1382,7 @@ const syncSecretsGitHub = async ({ ).data; // Get local copy of decrypted secrets. We cannot decrypt them as we dont have access to GH private key - const encryptedSecrets: GitHubSecretRes = ( + let encryptedSecrets: GitHubSecretRes = ( await octokit.request("GET /repos/{owner}/{repo}/actions/secrets", { owner: integration.owner, repo: integration.app @@ -1389,6 +1395,15 @@ const syncSecretsGitHub = async ({ {} ); + encryptedSecrets = Object.keys(encryptedSecrets).reduce((result: { + [key: string]: GitHubSecret; + }, key) => { + if ((appendices?.prefix !== undefined ? key.startsWith(appendices?.prefix) : true) && (appendices?.suffix !== undefined ? key.endsWith(appendices?.suffix) : true)) { + result[key] = encryptedSecrets[key]; + } + return result; + }, {}); + Object.keys(encryptedSecrets).map(async (key) => { if (!(key in secrets)) { await octokit.request("DELETE /repos/{owner}/{repo}/actions/secrets/{secret_name}", { @@ -2074,13 +2089,15 @@ const syncSecretsSupabase = async ({ const syncSecretsCheckly = async ({ integration, secrets, - accessToken + accessToken, + appendices }: { integration: IIntegration; secrets: Record; accessToken: string; + appendices?: { prefix: string, suffix: string }; }) => { - const getSecretsRes = ( + let getSecretsRes = ( await standardRequest.get(`${INTEGRATION_CHECKLY_API_URL}/v1/variables`, { headers: { Authorization: `Bearer ${accessToken}`, @@ -2096,6 +2113,15 @@ const syncSecretsCheckly = async ({ {} ); + getSecretsRes = Object.keys(getSecretsRes).reduce((result: { + [key: string]: string; + }, key) => { + if ((appendices?.prefix !== undefined ? key.startsWith(appendices?.prefix) : true) && (appendices?.suffix !== undefined ? key.endsWith(appendices?.suffix) : true)) { + result[key] = getSecretsRes[key]; + } + return result; + }, {}); + // add secrets for await (const key of Object.keys(secrets)) { if (!(key in getSecretsRes)) { diff --git a/backend/src/queues/integrations/syncSecretsToThirdPartyServices.ts b/backend/src/queues/integrations/syncSecretsToThirdPartyServices.ts index 7b6819b8c..b18d7bccc 100644 --- a/backend/src/queues/integrations/syncSecretsToThirdPartyServices.ts +++ b/backend/src/queues/integrations/syncSecretsToThirdPartyServices.ts @@ -60,7 +60,8 @@ syncSecretsToThirdPartyServices.process(async (job: Job) => { integrationAuth, secrets: Object.keys(suffixedSecrets).length !== 0 ? suffixedSecrets : secrets, accessId: access.accessId === undefined ? null : access.accessId, - accessToken: access.accessToken + accessToken: access.accessToken, + appendices: { prefix: integration.metadata?.secretPrefix || "", suffix: integration.metadata?.secretSuffix || "" } }); } }) diff --git a/frontend/src/pages/integrations/github/create.tsx b/frontend/src/pages/integrations/github/create.tsx index 5ca725a4b..bdc1579f7 100644 --- a/frontend/src/pages/integrations/github/create.tsx +++ b/frontend/src/pages/integrations/github/create.tsx @@ -5,6 +5,7 @@ import Link from "next/link"; import { useRouter } from "next/router"; import { faArrowUpRightFromSquare, faBookOpen, faBugs, faCircleInfo } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { motion } from "framer-motion"; import queryString from "query-string"; import { @@ -18,7 +19,11 @@ import { FormControl, Input, Select, - SelectItem + SelectItem, + Tab, + TabList, + TabPanel, + Tabs } from "../../../components/v2"; import { useGetIntegrationAuthApps, @@ -26,6 +31,11 @@ import { } from "../../../hooks/api/integrationAuth"; import { useGetWorkspaceById } from "../../../hooks/api/workspace"; +enum TabSections { + Connection = "connection", + Options = "options" +} + export default function GitHubCreateIntegrationPage() { const router = useRouter(); const { mutateAsync } = useCreateIntegration(); @@ -41,6 +51,7 @@ export default function GitHubCreateIntegrationPage() { const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState(""); const [secretPath, setSecretPath] = useState("/"); const [targetAppId, setTargetAppId] = useState(""); + const [secretSuffix, setSecretSuffix] = useState(""); const [isLoading, setIsLoading] = useState(false); @@ -78,7 +89,10 @@ export default function GitHubCreateIntegrationPage() { app: targetApp.name, sourceEnvironment: selectedSourceEnvironment, owner: targetApp.owner, - secretPath + secretPath, + metadata: { + secretSuffix + } }); setIsLoading(false); @@ -124,52 +138,87 @@ export default function GitHubCreateIntegrationPage() { - - - - - setSecretPath(evt.target.value)} - placeholder="Provide a path, default is /" - /> - - - setSelectedSourceEnvironment(val)} + className="w-full border border-mineshaft-500" > - {integrationAuthApp.name} - - )) - ) : ( - - No repositories found - - )} - - + {workspace?.environments.map((sourceEnvironment) => ( + + {sourceEnvironment.name} + + ))} + + + + setSecretPath(evt.target.value)} + placeholder="Provide a path, default is /" + /> + + + + + + + + + + setSecretSuffix(evt.target.value)} + placeholder="Provide a suffix for secret names, default is no suffix" + /> + + + + diff --git a/frontend/src/views/SecretApprovalPage/SecretApprovalPage.tsx b/frontend/src/views/SecretApprovalPage/SecretApprovalPage.tsx index c6e229970..c2c05310c 100644 --- a/frontend/src/views/SecretApprovalPage/SecretApprovalPage.tsx +++ b/frontend/src/views/SecretApprovalPage/SecretApprovalPage.tsx @@ -14,7 +14,7 @@ export const SecretApprovalPage = () => { const workspaceId = currentWorkspace?._id || ""; return ( -
+

Secret Approvals