Merge pull request #3806 from Infisical/feat/addHerokuSecretSync

feat(secret-sync): Add Heroku Secret Sync
This commit is contained in:
carlosmonastyrski
2025-06-19 15:28:56 -03:00
committed by GitHub
86 changed files with 1724 additions and 18 deletions
@@ -0,0 +1,76 @@
import { Controller, useFormContext, useWatch } from "react-hook-form";
import { SingleValue } from "react-select";
import { faCircleInfo } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField";
import { FilterableSelect, FormControl, Tooltip } from "@app/components/v2";
import { THerokuApp } from "@app/hooks/api/appConnections/heroku";
import { useHerokuConnectionListApps } from "@app/hooks/api/appConnections/heroku/queries";
import { SecretSync } from "@app/hooks/api/secretSyncs";
import { TSecretSyncForm } from "../schemas";
export const HerokuSyncFields = () => {
const { control, setValue } = useFormContext<
TSecretSyncForm & { destination: SecretSync.Heroku }
>();
const connectionId = useWatch({ name: "connection.id", control });
const { data: apps, isLoading: isAppsLoading } = useHerokuConnectionListApps(connectionId, {
enabled: Boolean(connectionId)
});
return (
<>
<SecretSyncConnectionField
onChange={() => {
setValue("destinationConfig.app", "");
setValue("destinationConfig.appName", "");
}}
/>
<Controller
name="destinationConfig.app"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error)}
errorText={error?.message}
label="App"
helperText={
<Tooltip
className="max-w-md"
content="Ensure the app exists in the connection's Heroku instance URL."
>
<div>
<span>Don&#39;t see the app you&#39;re looking for?</span>{" "}
<FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-400" />
</div>
</Tooltip>
}
>
<FilterableSelect
menuPlacement="top"
isLoading={isAppsLoading && Boolean(connectionId)}
isDisabled={!connectionId}
value={apps?.find((app) => app.id === value) ?? null}
onChange={(option) => {
onChange((option as SingleValue<THerokuApp>)?.id ?? "");
setValue(
"destinationConfig.appName",
(option as SingleValue<THerokuApp>)?.name ?? ""
);
}}
options={apps}
placeholder="Select an app..."
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.id}
/>
</FormControl>
)}
/>
</>
);
};
@@ -15,6 +15,7 @@ import { FlyioSyncFields } from "./FlyioSyncFields";
import { GcpSyncFields } from "./GcpSyncFields";
import { GitHubSyncFields } from "./GitHubSyncFields";
import { HCVaultSyncFields } from "./HCVaultSyncFields";
import { HerokuSyncFields } from "./HerokuSyncFields";
import { HumanitecSyncFields } from "./HumanitecSyncFields";
import { OCIVaultSyncFields } from "./OCIVaultSyncFields";
import { RenderSyncFields } from "./RenderSyncFields";
@@ -63,6 +64,8 @@ export const SecretSyncDestinationFields = () => {
return <OCIVaultSyncFields />;
case SecretSync.OnePass:
return <OnePassSyncFields />;
case SecretSync.Heroku:
return <HerokuSyncFields />;
case SecretSync.Render:
return <RenderSyncFields />;
case SecretSync.Flyio:
@@ -52,6 +52,7 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
case SecretSync.TeamCity:
case SecretSync.OnePass:
case SecretSync.OCIVault:
case SecretSync.Heroku:
case SecretSync.Render:
case SecretSync.Flyio:
AdditionalSyncOptionsFieldsComponent = null;
@@ -0,0 +1,18 @@
import { useFormContext } from "react-hook-form";
import { GenericFieldLabel } from "@app/components/secret-syncs";
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
import { SecretSync } from "@app/hooks/api/secretSyncs";
export const HerokuSyncReviewFields = () => {
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.Heroku }>();
const appName = watch("destinationConfig.appName");
const appId = watch("destinationConfig.app");
return (
<>
<GenericFieldLabel label="App">{appName}</GenericFieldLabel>
<GenericFieldLabel label="App ID">{appId}</GenericFieldLabel>
</>
);
};
@@ -24,6 +24,7 @@ import { FlyioSyncReviewFields } from "./FlyioSyncReviewFields";
import { GcpSyncReviewFields } from "./GcpSyncReviewFields";
import { GitHubSyncReviewFields } from "./GitHubSyncReviewFields";
import { HCVaultSyncReviewFields } from "./HCVaultSyncReviewFields";
import { HerokuSyncReviewFields } from "./HerokuSyncReviewFields";
import { HumanitecSyncReviewFields } from "./HumanitecSyncReviewFields";
import { OCIVaultSyncReviewFields } from "./OCIVaultSyncReviewFields";
import { OnePassSyncReviewFields } from "./OnePassSyncReviewFields";
@@ -106,6 +107,9 @@ export const SecretSyncReviewFields = () => {
case SecretSync.OnePass:
DestinationFieldsComponent = <OnePassSyncReviewFields />;
break;
case SecretSync.Heroku:
DestinationFieldsComponent = <HerokuSyncReviewFields />;
break;
case SecretSync.Render:
DestinationFieldsComponent = <RenderSyncReviewFields />;
break;
@@ -0,0 +1,14 @@
import { z } from "zod";
import { BaseSecretSyncSchema } from "@app/components/secret-syncs/forms/schemas/base-secret-sync-schema";
import { SecretSync } from "@app/hooks/api/secretSyncs";
export const HerokuSyncDestinationSchema = BaseSecretSyncSchema().merge(
z.object({
destination: z.literal(SecretSync.Heroku),
destinationConfig: z.object({
app: z.string().trim().min(1, "App ID required"),
appName: z.string().trim().min(1, "App name required")
})
})
);
@@ -12,6 +12,7 @@ import { FlyioSyncDestinationSchema } from "./flyio-sync-destination-schema";
import { GcpSyncDestinationSchema } from "./gcp-sync-destination-schema";
import { GitHubSyncDestinationSchema } from "./github-sync-destination-schema";
import { HCVaultSyncDestinationSchema } from "./hc-vault-sync-destination-schema";
import { HerokuSyncDestinationSchema } from "./heroku-sync-destination-schema";
import { HumanitecSyncDestinationSchema } from "./humanitec-sync-destination-schema";
import { OCIVaultSyncDestinationSchema } from "./oci-vault-sync-destination-schema";
import { RenderSyncDestinationSchema } from "./render-sync-destination-schema";
@@ -38,6 +39,7 @@ const SecretSyncUnionSchema = z.discriminatedUnion("destination", [
TeamCitySyncDestinationSchema,
OCIVaultSyncDestinationSchema,
OnePassSyncDestinationSchema,
HerokuSyncDestinationSchema,
RenderSyncDestinationSchema,
FlyioSyncDestinationSchema
]);