mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 21:27:10 +00:00
fix(integrations/circle-ci): Refactored Circle CI integration
The integration seemingly never worked in the first place due to inpropper project slugs. This PR resolves it.
This commit is contained in:
@@ -459,17 +459,22 @@ const getAppsFlyio = async ({ accessToken }: { accessToken: string }) => {
|
|||||||
* Return list of projects for CircleCI integration
|
* Return list of projects for CircleCI integration
|
||||||
*/
|
*/
|
||||||
const getAppsCircleCI = async ({ accessToken }: { accessToken: string }) => {
|
const getAppsCircleCI = async ({ accessToken }: { accessToken: string }) => {
|
||||||
const res = (
|
// Fetch collaborations (v2 API)
|
||||||
await request.get<{ reponame: string }[]>(`${IntegrationUrls.CIRCLECI_API_URL}/v1.1/projects`, {
|
const collaborations = (
|
||||||
headers: {
|
await request.get<{ id: string; name: string; slug: string }[]>(
|
||||||
"Circle-Token": accessToken,
|
`${IntegrationUrls.CIRCLECI_API_URL}/v2/me/collaborations`,
|
||||||
"Accept-Encoding": "application/json"
|
{
|
||||||
|
headers: {
|
||||||
|
"Circle-Token": accessToken,
|
||||||
|
"Accept-Encoding": "application/json"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
).data;
|
).data;
|
||||||
|
|
||||||
const apps = res?.map((a) => ({
|
const apps = collaborations.map((a) => ({
|
||||||
name: a?.reponame
|
name: a.name,
|
||||||
|
appId: a.id
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return apps;
|
return apps;
|
||||||
|
|||||||
@@ -143,6 +143,12 @@ export type TBitbucketWorkspace = {
|
|||||||
updated_on: string;
|
updated_on: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum CircleCiVcsType {
|
||||||
|
GitHub = "GitHub",
|
||||||
|
CircleCI = "CircleCI",
|
||||||
|
BitBucket = "BitBucket"
|
||||||
|
}
|
||||||
|
|
||||||
export type TNorthflankSecretGroup = {
|
export type TNorthflankSecretGroup = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import { TCreateManySecretsRawFn, TUpdateManySecretsRawFn } from "@app/services/
|
|||||||
|
|
||||||
import { TIntegrationDALFactory } from "../integration/integration-dal";
|
import { TIntegrationDALFactory } from "../integration/integration-dal";
|
||||||
import { IntegrationMetadataSchema } from "../integration/integration-schema";
|
import { IntegrationMetadataSchema } from "../integration/integration-schema";
|
||||||
import { TIntegrationsWithEnvironment } from "./integration-auth-types";
|
import { CircleCiVcsType, TIntegrationsWithEnvironment } from "./integration-auth-types";
|
||||||
import {
|
import {
|
||||||
IntegrationInitialSyncBehavior,
|
IntegrationInitialSyncBehavior,
|
||||||
IntegrationMappingBehavior,
|
IntegrationMappingBehavior,
|
||||||
@@ -1930,21 +1930,48 @@ const syncSecretsCircleCI = async ({
|
|||||||
accessToken: string;
|
accessToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
const circleciOrganizationDetail = (
|
const circleciOrganizationDetail = (
|
||||||
await request.get(`${IntegrationUrls.CIRCLECI_API_URL}/v2/me/collaborations`, {
|
await request.get<{ slug: string; name: string }[]>(`${IntegrationUrls.CIRCLECI_API_URL}/v2/me/collaborations`, {
|
||||||
headers: {
|
headers: {
|
||||||
"Circle-Token": accessToken,
|
"Circle-Token": accessToken,
|
||||||
"Accept-Encoding": "application/json"
|
"Accept-Encoding": "application/json"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
).data[0];
|
).data;
|
||||||
|
|
||||||
const { slug } = circleciOrganizationDetail;
|
let orgSlug: string | null = null;
|
||||||
|
if (!integration.owner) {
|
||||||
|
orgSlug = `${circleciOrganizationDetail[0].slug}/${integration.app}`;
|
||||||
|
} else {
|
||||||
|
const projectDetails = (
|
||||||
|
await request.get<{ vcs_info: { provider: CircleCiVcsType } }>(
|
||||||
|
`${IntegrationUrls.CIRCLECI_API_URL}/v2/project/${integration.app}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Circle-Token": accessToken,
|
||||||
|
"Accept-Encoding": "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
).data;
|
||||||
|
|
||||||
|
const vcsProviderMap: Record<CircleCiVcsType, string> = {
|
||||||
|
[CircleCiVcsType.GitHub]: "gh",
|
||||||
|
[CircleCiVcsType.BitBucket]: "bb",
|
||||||
|
[CircleCiVcsType.CircleCI]: "circleci"
|
||||||
|
};
|
||||||
|
|
||||||
|
orgSlug = `${vcsProviderMap[projectDetails.vcs_info.provider]}/${integration.owner}/${integration.app}`;
|
||||||
|
|
||||||
|
if (!orgSlug) {
|
||||||
|
throw new Error("CircleCI: Organization not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// sync secrets to CircleCI
|
// sync secrets to CircleCI
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
Object.keys(secrets).map(async (key) =>
|
Object.keys(secrets).map(async (key) =>
|
||||||
request.post(
|
request.post(
|
||||||
`${IntegrationUrls.CIRCLECI_API_URL}/v2/project/${slug}/${integration.app}/envvar`,
|
`${IntegrationUrls.CIRCLECI_API_URL}/v2/project/${orgSlug}/envvar`,
|
||||||
{
|
{
|
||||||
name: key,
|
name: key,
|
||||||
value: secrets[key].value
|
value: secrets[key].value
|
||||||
@@ -1962,7 +1989,7 @@ const syncSecretsCircleCI = async ({
|
|||||||
// get secrets from CircleCI
|
// get secrets from CircleCI
|
||||||
const getSecretsRes = (
|
const getSecretsRes = (
|
||||||
await request.get<{ items: { name: string }[] }>(
|
await request.get<{ items: { name: string }[] }>(
|
||||||
`${IntegrationUrls.CIRCLECI_API_URL}/v2/project/${slug}/${integration.app}/envvar`,
|
`${IntegrationUrls.CIRCLECI_API_URL}/v2/project/${orgSlug}/envvar`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"Circle-Token": accessToken,
|
"Circle-Token": accessToken,
|
||||||
@@ -1976,15 +2003,12 @@ const syncSecretsCircleCI = async ({
|
|||||||
await Promise.all(
|
await Promise.all(
|
||||||
getSecretsRes.map(async (sec) => {
|
getSecretsRes.map(async (sec) => {
|
||||||
if (!(sec.name in secrets)) {
|
if (!(sec.name in secrets)) {
|
||||||
return request.delete(
|
return request.delete(`${IntegrationUrls.CIRCLECI_API_URL}/v2/project/${orgSlug}/envvar/${sec.name}`, {
|
||||||
`${IntegrationUrls.CIRCLECI_API_URL}/v2/project/${slug}/${integration.app}/envvar/${sec.name}`,
|
headers: {
|
||||||
{
|
"Circle-Token": accessToken,
|
||||||
headers: {
|
"Content-Type": "application/json"
|
||||||
"Circle-Token": accessToken,
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ export default function CircleCICreateIntegrationPage() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
|
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
|
||||||
|
const [targetOrganization, setTargetOrganization] = useState("");
|
||||||
const [secretPath, setSecretPath] = useState("/");
|
const [secretPath, setSecretPath] = useState("/");
|
||||||
|
|
||||||
const [targetApp, setTargetApp] = useState("");
|
const [targetApp, setTargetApp] = useState("");
|
||||||
@@ -57,26 +58,19 @@ export default function CircleCICreateIntegrationPage() {
|
|||||||
}
|
}
|
||||||
}, [workspace]);
|
}, [workspace]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (integrationAuthApps) {
|
|
||||||
if (integrationAuthApps.length > 0) {
|
|
||||||
setTargetApp(integrationAuthApps[0]?.name);
|
|
||||||
} else {
|
|
||||||
setTargetApp("none");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [integrationAuthApps]);
|
|
||||||
|
|
||||||
const handleButtonClick = async () => {
|
const handleButtonClick = async () => {
|
||||||
try {
|
try {
|
||||||
if (!integrationAuth?.id) return;
|
if (!integrationAuth?.id) return;
|
||||||
|
|
||||||
|
if (!targetApp || targetOrganization === "none") return;
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
integrationAuthId: integrationAuth?.id,
|
integrationAuthId: integrationAuth?.id,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
app: targetApp,
|
app: targetApp,
|
||||||
|
owner: targetOrganization,
|
||||||
appId: integrationAuthApps?.find(
|
appId: integrationAuthApps?.find(
|
||||||
(integrationAuthApp) => integrationAuthApp.name === targetApp
|
(integrationAuthApp) => integrationAuthApp.name === targetApp
|
||||||
)?.appId,
|
)?.appId,
|
||||||
@@ -92,11 +86,7 @@ export default function CircleCICreateIntegrationPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return integrationAuth &&
|
return integrationAuth && workspace && selectedSourceEnvironment && integrationAuthApps ? (
|
||||||
workspace &&
|
|
||||||
selectedSourceEnvironment &&
|
|
||||||
integrationAuthApps &&
|
|
||||||
targetApp ? (
|
|
||||||
<div className="flex h-full w-full flex-col items-center justify-center">
|
<div className="flex h-full w-full flex-col items-center justify-center">
|
||||||
<Head>
|
<Head>
|
||||||
<title>Set Up CircleCI Integration</title>
|
<title>Set Up CircleCI Integration</title>
|
||||||
@@ -108,7 +98,7 @@ export default function CircleCICreateIntegrationPage() {
|
|||||||
subTitle="Choose which environment or folder in Infisical you want to sync to CircleCI environment variables."
|
subTitle="Choose which environment or folder in Infisical you want to sync to CircleCI environment variables."
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center">
|
<div className="flex flex-row items-center">
|
||||||
<div className="inline flex items-center pb-0.5">
|
<div className="flex items-center pb-0.5">
|
||||||
<Image
|
<Image
|
||||||
src="/images/integrations/Circle CI.png"
|
src="/images/integrations/Circle CI.png"
|
||||||
height={30}
|
height={30}
|
||||||
@@ -131,6 +121,7 @@ export default function CircleCICreateIntegrationPage() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
|
|
||||||
<FormControl label="Project Environment" className="px-6">
|
<FormControl label="Project Environment" className="px-6">
|
||||||
<Select
|
<Select
|
||||||
value={selectedSourceEnvironment}
|
value={selectedSourceEnvironment}
|
||||||
@@ -154,29 +145,42 @@ export default function CircleCICreateIntegrationPage() {
|
|||||||
placeholder="Provide a path, default is /"
|
placeholder="Provide a path, default is /"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl label="CircleCI Project" className="px-6">
|
|
||||||
|
<FormControl label="CircleCI Organization" className="px-6">
|
||||||
<Select
|
<Select
|
||||||
value={targetApp}
|
value={targetOrganization}
|
||||||
onValueChange={(val) => setTargetApp(val)}
|
onValueChange={(val) => {
|
||||||
|
setTargetOrganization(val);
|
||||||
|
}}
|
||||||
className="w-full border border-mineshaft-500"
|
className="w-full border border-mineshaft-500"
|
||||||
isDisabled={integrationAuthApps.length === 0}
|
isDisabled={integrationAuthApps.length === 0}
|
||||||
>
|
>
|
||||||
{integrationAuthApps.length > 0 ? (
|
{integrationAuthApps.length > 0 ? (
|
||||||
integrationAuthApps.map((integrationAuthApp) => (
|
integrationAuthApps.map((integrationAuthApp) => (
|
||||||
<SelectItem
|
<SelectItem
|
||||||
value={integrationAuthApp.name}
|
value={integrationAuthApp.appId!}
|
||||||
key={`target-app-${integrationAuthApp.name}`}
|
key={`target-org-${integrationAuthApp.owner}`}
|
||||||
>
|
>
|
||||||
{integrationAuthApp.name}
|
{integrationAuthApp.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<SelectItem value="none" key="target-app-none">
|
<SelectItem value="none" key="target-app-none">
|
||||||
No projects found
|
No organizations found
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
)}
|
)}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
|
{targetOrganization && (
|
||||||
|
<FormControl label="CircleCI Project ID" className="px-6">
|
||||||
|
<Input
|
||||||
|
placeholder="55a2071d-97b5-4f71-b285-990d73c41268"
|
||||||
|
value={targetApp}
|
||||||
|
onChange={(evt) => setTargetApp(evt.target.value)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
colorSchema="primary"
|
colorSchema="primary"
|
||||||
|
|||||||
+1
@@ -99,6 +99,7 @@ export const ConfiguredIntegrationItem = ({
|
|||||||
<FormLabel
|
<FormLabel
|
||||||
label={
|
label={
|
||||||
(integration.integration === "qovery" && integration?.scope) ||
|
(integration.integration === "qovery" && integration?.scope) ||
|
||||||
|
(integration.integration === "circleci" && "Project ID") ||
|
||||||
(integration.integration === "aws-secret-manager" && "Secret") ||
|
(integration.integration === "aws-secret-manager" && "Secret") ||
|
||||||
(["aws-parameter-store", "rundeck"].includes(integration.integration) && "Path") ||
|
(["aws-parameter-store", "rundeck"].includes(integration.integration) && "Path") ||
|
||||||
(integration?.integration === "terraform-cloud" && "Project") ||
|
(integration?.integration === "terraform-cloud" && "Project") ||
|
||||||
|
|||||||
Reference in New Issue
Block a user