diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts
index b6c00985a..318c085a1 100644
--- a/backend/src/lib/api-docs/constants.ts
+++ b/backend/src/lib/api-docs/constants.ts
@@ -2373,6 +2373,10 @@ export const SecretSyncs = {
keyId: "The AWS KMS key ID or alias to use when encrypting parameters synced by Infisical.",
tags: "Optional tags to add to secrets synced by Infisical.",
syncSecretMetadataAsTags: `Whether Infisical secret metadata should be added as tags to secrets synced by Infisical.`
+ },
+ RENDER: {
+ autoRedeployServices:
+ "Whether Infisical should automatically redeploy the configured Render service upon secret changes."
}
},
DESTINATION_CONFIG: {
diff --git a/backend/src/services/secret-sync/render/render-sync-fns.ts b/backend/src/services/secret-sync/render/render-sync-fns.ts
index 36e97e620..8af3653ca 100644
--- a/backend/src/services/secret-sync/render/render-sync-fns.ts
+++ b/backend/src/services/secret-sync/render/render-sync-fns.ts
@@ -97,6 +97,28 @@ const batchUpdateEnvironmentSecrets = async (
);
};
+const redeployService = async (secretSync: TRenderSyncWithCredentials) => {
+ const {
+ destinationConfig,
+ connection: {
+ credentials: { apiKey }
+ }
+ } = secretSync;
+
+ await makeRequestWithRetry(() =>
+ request.post(
+ `${IntegrationUrls.RENDER_API_URL}/v1/services/${destinationConfig.serviceId}/deploys`,
+ {},
+ {
+ headers: {
+ Authorization: `Bearer ${apiKey}`,
+ "Accept-Encoding": "application/json"
+ }
+ }
+ )
+ );
+};
+
export const RenderSyncFns = {
syncSecrets: async (secretSync: TRenderSyncWithCredentials, secretMap: TSecretMap) => {
const renderSecrets = await getRenderEnvironmentSecrets(secretSync);
@@ -131,6 +153,10 @@ export const RenderSyncFns = {
}
await batchUpdateEnvironmentSecrets(secretSync, finalEnvVars);
+
+ if (secretSync.syncOptions.autoRedeployServices) {
+ await redeployService(secretSync);
+ }
},
getSecrets: async (secretSync: TRenderSyncWithCredentials): Promise => {
@@ -151,5 +177,9 @@ export const RenderSyncFns = {
}
}
await batchUpdateEnvironmentSecrets(secretSync, finalEnvVars);
+
+ if (secretSync.syncOptions.autoRedeployServices) {
+ await redeployService(secretSync);
+ }
}
};
diff --git a/backend/src/services/secret-sync/render/render-sync-schemas.ts b/backend/src/services/secret-sync/render/render-sync-schemas.ts
index 77414c17c..0d6e93987 100644
--- a/backend/src/services/secret-sync/render/render-sync-schemas.ts
+++ b/backend/src/services/secret-sync/render/render-sync-schemas.ts
@@ -20,23 +20,33 @@ const RenderSyncDestinationConfigSchema = z.discriminatedUnion("scope", [
})
]);
+const RenderSyncOptionsSchema = z.object({
+ autoRedeployServices: z.boolean().optional().describe(SecretSyncs.ADDITIONAL_SYNC_OPTIONS.RENDER.autoRedeployServices)
+});
+
const RenderSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };
-export const RenderSyncSchema = BaseSecretSyncSchema(SecretSync.Render, RenderSyncOptionsConfig).extend({
+export const RenderSyncSchema = BaseSecretSyncSchema(
+ SecretSync.Render,
+ RenderSyncOptionsConfig,
+ RenderSyncOptionsSchema
+).extend({
destination: z.literal(SecretSync.Render),
destinationConfig: RenderSyncDestinationConfigSchema
});
export const CreateRenderSyncSchema = GenericCreateSecretSyncFieldsSchema(
SecretSync.Render,
- RenderSyncOptionsConfig
+ RenderSyncOptionsConfig,
+ RenderSyncOptionsSchema
).extend({
destinationConfig: RenderSyncDestinationConfigSchema
});
export const UpdateRenderSyncSchema = GenericUpdateSecretSyncFieldsSchema(
SecretSync.Render,
- RenderSyncOptionsConfig
+ RenderSyncOptionsConfig,
+ RenderSyncOptionsSchema
).extend({
destinationConfig: RenderSyncDestinationConfigSchema.optional()
});
diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/RenderSyncOptionsFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/RenderSyncOptionsFields.tsx
new file mode 100644
index 000000000..6d4173bd0
--- /dev/null
+++ b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/RenderSyncOptionsFields.tsx
@@ -0,0 +1,40 @@
+import { Controller, useFormContext } from "react-hook-form";
+import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+
+import { FormControl, Switch, Tooltip } from "@app/components/v2";
+import { SecretSync } from "@app/hooks/api/secretSyncs";
+
+import { TSecretSyncForm } from "../schemas";
+
+export const RenderSyncOptionsFields = () => {
+ const { control } = useFormContext();
+
+ return (
+ (
+
+
+ Auto Redeploy Services On Sync
+ If enabled, services will be automatically redeployed upon secret changes.
+ }
+ >
+
+
+
+
+ )}
+ />
+ );
+};
diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx
index 50ea46ac0..cb7a40559 100644
--- a/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx
+++ b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx
@@ -14,6 +14,7 @@ import { SecretSync, useSecretSyncOption } from "@app/hooks/api/secretSyncs";
import { TSecretSyncForm } from "../schemas";
import { AwsParameterStoreSyncOptionsFields } from "./AwsParameterStoreSyncOptionsFields";
import { AwsSecretsManagerSyncOptionsFields } from "./AwsSecretsManagerSyncOptionsFields";
+import { RenderSyncOptionsFields } from "./RenderSyncOptionsFields";
type Props = {
hideInitialSync?: boolean;
@@ -38,6 +39,9 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
case SecretSync.AWSSecretsManager:
AdditionalSyncOptionsFieldsComponent = ;
break;
+ case SecretSync.Render:
+ AdditionalSyncOptionsFieldsComponent = ;
+ break;
case SecretSync.GitHub:
case SecretSync.GCPSecretManager:
case SecretSync.AzureKeyVault:
@@ -54,7 +58,6 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
case SecretSync.OnePass:
case SecretSync.OCIVault:
case SecretSync.Heroku:
- case SecretSync.Render:
case SecretSync.Flyio:
case SecretSync.GitLab:
case SecretSync.CloudflarePages:
diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/RenderSyncReviewFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/RenderSyncReviewFields.tsx
index becc46c1d..15f5b6625 100644
--- a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/RenderSyncReviewFields.tsx
+++ b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/RenderSyncReviewFields.tsx
@@ -2,8 +2,29 @@ import { useFormContext } from "react-hook-form";
import { GenericFieldLabel } from "@app/components/secret-syncs";
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
+import { Badge } from "@app/components/v2";
import { SecretSync } from "@app/hooks/api/secretSyncs";
+export const RenderSyncOptionsReviewFields = () => {
+ const { watch } = useFormContext();
+
+ const [{ autoRedeployServices }] = watch(["syncOptions"]);
+
+ return (
+
+ {autoRedeployServices ? (
+
+ Enabled
+
+ ) : (
+
+ Disabled
+
+ )}
+
+ );
+};
+
export const RenderSyncReviewFields = () => {
const { watch } = useFormContext();
const serviceName = watch("destinationConfig.serviceName");
diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx
index c1194a4c1..5ee53f2e3 100644
--- a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx
+++ b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx
@@ -35,7 +35,7 @@ import { HumanitecSyncReviewFields } from "./HumanitecSyncReviewFields";
import { OCIVaultSyncReviewFields } from "./OCIVaultSyncReviewFields";
import { OnePassSyncReviewFields } from "./OnePassSyncReviewFields";
import { RailwaySyncReviewFields } from "./RailwaySyncReviewFields";
-import { RenderSyncReviewFields } from "./RenderSyncReviewFields";
+import { RenderSyncOptionsReviewFields, RenderSyncReviewFields } from "./RenderSyncReviewFields";
import { SupabaseSyncReviewFields } from "./SupabaseSyncReviewFields";
import { TeamCitySyncReviewFields } from "./TeamCitySyncReviewFields";
import { TerraformCloudSyncReviewFields } from "./TerraformCloudSyncReviewFields";
@@ -121,6 +121,7 @@ export const SecretSyncReviewFields = () => {
break;
case SecretSync.Render:
DestinationFieldsComponent = ;
+ AdditionalSyncOptionsFieldsComponent = ;
break;
case SecretSync.Flyio:
DestinationFieldsComponent = ;
diff --git a/frontend/src/components/secret-syncs/forms/schemas/render-sync-destination-schema.ts b/frontend/src/components/secret-syncs/forms/schemas/render-sync-destination-schema.ts
index 16b213421..da81e121d 100644
--- a/frontend/src/components/secret-syncs/forms/schemas/render-sync-destination-schema.ts
+++ b/frontend/src/components/secret-syncs/forms/schemas/render-sync-destination-schema.ts
@@ -4,7 +4,11 @@ import { BaseSecretSyncSchema } from "@app/components/secret-syncs/forms/schemas
import { SecretSync } from "@app/hooks/api/secretSyncs";
import { RenderSyncScope, RenderSyncType } from "@app/hooks/api/secretSyncs/render-sync";
-export const RenderSyncDestinationSchema = BaseSecretSyncSchema().merge(
+export const RenderSyncDestinationSchema = BaseSecretSyncSchema(
+ z.object({
+ autoRedeployServices: z.boolean().optional()
+ })
+).merge(
z.object({
destination: z.literal(SecretSync.Render),
destinationConfig: z.discriminatedUnion("scope", [
diff --git a/frontend/src/hooks/api/secretSyncs/render-sync.ts b/frontend/src/hooks/api/secretSyncs/render-sync.ts
index ecac7d077..61aecc53f 100644
--- a/frontend/src/hooks/api/secretSyncs/render-sync.ts
+++ b/frontend/src/hooks/api/secretSyncs/render-sync.ts
@@ -16,6 +16,10 @@ export type TRenderSync = TRootSecretSync & {
name: string;
id: string;
};
+
+ syncOptions: {
+ autoRedeployServices?: boolean;
+ };
};
export enum RenderSyncScope {
diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/RenderSyncOptionsSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/RenderSyncOptionsSection.tsx
new file mode 100644
index 000000000..3dd3d2fd5
--- /dev/null
+++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/RenderSyncOptionsSection.tsx
@@ -0,0 +1,27 @@
+import { GenericFieldLabel } from "@app/components/secret-syncs";
+import { Badge } from "@app/components/v2";
+import { TRenderSync } from "@app/hooks/api/secretSyncs/render-sync";
+
+type Props = {
+ secretSync: TRenderSync;
+};
+
+export const RenderSyncOptionsSection = ({ secretSync }: Props) => {
+ const {
+ syncOptions: { autoRedeployServices }
+ } = secretSync;
+
+ return (
+
+ {autoRedeployServices ? (
+
+ Enabled
+
+ ) : (
+
+ Disabled
+
+ )}
+
+ );
+};
diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx
index 843e02f63..32c46fca2 100644
--- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx
+++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx
@@ -13,6 +13,7 @@ import { SecretSync, TSecretSync } from "@app/hooks/api/secretSyncs";
import { AwsParameterStoreSyncOptionsSection } from "./AwsParameterStoreSyncOptionsSection";
import { AwsSecretsManagerSyncOptionsSection } from "./AwsSecretsManagerSyncOptionsSection";
+import { RenderSyncOptionsSection } from "./RenderSyncOptionsSection";
type Props = {
secretSync: TSecretSync;
@@ -40,6 +41,9 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) =
);
break;
+ case SecretSync.Render:
+ AdditionalSyncOptionsComponent = ;
+ break;
case SecretSync.GitHub:
case SecretSync.GCPSecretManager:
case SecretSync.AzureKeyVault:
@@ -56,7 +60,6 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) =
case SecretSync.OCIVault:
case SecretSync.OnePass:
case SecretSync.Heroku:
- case SecretSync.Render:
case SecretSync.Flyio:
case SecretSync.GitLab:
case SecretSync.CloudflarePages: