allow multiple simultaneous integrations with checkly and github

This commit is contained in:
Vladyslav Matsiiako
2023-10-08 13:53:54 -07:00
parent 60bd5e57fc
commit 5971480ca9
4 changed files with 132 additions and 56 deletions

View File

@@ -87,13 +87,15 @@ const syncSecrets = async ({
integrationAuth,
secrets,
accessId,
accessToken
accessToken,
appendices
}: {
integration: IIntegration;
integrationAuth: IIntegrationAuth;
secrets: Record<string, { value: string; comment?: string }>;
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<string, { value: string; comment?: string }>;
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<string, { value: string; comment?: string }>;
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)) {

View File

@@ -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 || "" }
});
}
})

View File

@@ -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() {
</Link>
</div>
</CardTitle>
<FormControl label="Project Environment" className="px-6">
<Select
value={selectedSourceEnvironment}
onValueChange={(val) => setSelectedSourceEnvironment(val)}
className="w-full border border-mineshaft-500"
>
{workspace?.environments.map((sourceEnvironment) => (
<SelectItem
value={sourceEnvironment.slug}
key={`azure-key-vault-environment-${sourceEnvironment.slug}`}
>
{sourceEnvironment.name}
</SelectItem>
))}
</Select>
</FormControl>
<FormControl label="Secrets Path" className="px-6">
<Input
value={secretPath}
onChange={(evt) => setSecretPath(evt.target.value)}
placeholder="Provide a path, default is /"
/>
</FormControl>
<FormControl label="GitHub Repo" className="px-6">
<Select
value={targetAppId}
onValueChange={(val) => setTargetAppId(val)}
className="w-full border border-mineshaft-500"
isDisabled={integrationAuthApps.length === 0}
>
{integrationAuthApps.length > 0 ? (
integrationAuthApps.map((integrationAuthApp) => (
<SelectItem
value={integrationAuthApp.appId as string}
key={`github-repo-${integrationAuthApp.appId}`}
<Tabs defaultValue={TabSections.Connection} className="px-6">
<TabList>
<div className="flex flex-row border-b border-mineshaft-600 w-full">
<Tab value={TabSections.Connection}>Connection</Tab>
<Tab value={TabSections.Options}>Options</Tab>
</div>
</TabList>
<TabPanel value={TabSections.Connection}>
<motion.div
key="panel-1"
transition={{ duration: 0.15 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }}
>
<FormControl label="Project Environment">
<Select
value={selectedSourceEnvironment}
onValueChange={(val) => setSelectedSourceEnvironment(val)}
className="w-full border border-mineshaft-500"
>
{integrationAuthApp.name}
</SelectItem>
))
) : (
<SelectItem value="none" key="target-app-none">
No repositories found
</SelectItem>
)}
</Select>
</FormControl>
{workspace?.environments.map((sourceEnvironment) => (
<SelectItem
value={sourceEnvironment.slug}
key={`azure-key-vault-environment-${sourceEnvironment.slug}`}
>
{sourceEnvironment.name}
</SelectItem>
))}
</Select>
</FormControl>
<FormControl label="Secrets Path">
<Input
value={secretPath}
onChange={(evt) => setSecretPath(evt.target.value)}
placeholder="Provide a path, default is /"
/>
</FormControl>
<FormControl label="GitHub Repo">
<Select
value={targetAppId}
onValueChange={(val) => setTargetAppId(val)}
className="w-full border border-mineshaft-500"
isDisabled={integrationAuthApps.length === 0}
>
{integrationAuthApps.length > 0 ? (
integrationAuthApps.map((integrationAuthApp) => (
<SelectItem
value={integrationAuthApp.appId as string}
key={`github-repo-${integrationAuthApp.appId}`}
>
{integrationAuthApp.name}
</SelectItem>
))
) : (
<SelectItem value="none" key="target-app-none">
No repositories found
</SelectItem>
)}
</Select>
</FormControl>
</motion.div>
</TabPanel>
<TabPanel value={TabSections.Options}>
<motion.div
key="panel-1"
transition={{ duration: 0.15 }}
initial={{ opacity: 0, translateX: -30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }}
>
<FormControl label="Append Secret Names with..." className="pb-[9.75rem]">
<Input
value={secretSuffix}
onChange={(evt) => setSecretSuffix(evt.target.value)}
placeholder="Provide a suffix for secret names, default is no suffix"
/>
</FormControl>
</motion.div>
</TabPanel>
</Tabs>
<Button
onClick={handleButtonClick}
color="mineshaft"

View File

@@ -139,7 +139,7 @@ export const IntegrationsSection = ({
</div>
</div>
)}
{(integration.integration === "checkly") && (
{((integration.integration === "checkly") || (integration.integration === "github")) && (
<div className="ml-2 flex flex-col">
<FormLabel label="Secret Suffix" />
<div className="rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">