mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat(secret-sync/render): auto redeploy on sync
This commit is contained in:
@@ -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: {
|
||||
|
||||
@@ -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<TSecretMap> => {
|
||||
@@ -151,5 +177,9 @@ export const RenderSyncFns = {
|
||||
}
|
||||
}
|
||||
await batchUpdateEnvironmentSecrets(secretSync, finalEnvVars);
|
||||
|
||||
if (secretSync.syncOptions.autoRedeployServices) {
|
||||
await redeployService(secretSync);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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()
|
||||
});
|
||||
|
||||
@@ -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<TSecretSyncForm & { destination: SecretSync.Render }>();
|
||||
|
||||
return (
|
||||
<Controller
|
||||
name="syncOptions.autoRedeployServices"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl className="mt-4" isError={Boolean(error?.message)} errorText={error?.message}>
|
||||
<Switch
|
||||
className="bg-mineshaft-400/50 shadow-inner data-[state=checked]:bg-green/80"
|
||||
id="auto-redeploy-services"
|
||||
thumbClassName="bg-mineshaft-800"
|
||||
isChecked={value}
|
||||
onCheckedChange={onChange}
|
||||
>
|
||||
Auto Redeploy Services On Sync
|
||||
<Tooltip
|
||||
className="max-w-md"
|
||||
content={
|
||||
<p>If enabled, services will be automatically redeployed upon secret changes.</p>
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon icon={faQuestionCircle} size="sm" className="ml-1" />
|
||||
</Tooltip>
|
||||
</Switch>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -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 = <AwsSecretsManagerSyncOptionsFields />;
|
||||
break;
|
||||
case SecretSync.Render:
|
||||
AdditionalSyncOptionsFieldsComponent = <RenderSyncOptionsFields />;
|
||||
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:
|
||||
|
||||
@@ -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<TSecretSyncForm & { destination: SecretSync.Render }>();
|
||||
|
||||
const [{ autoRedeployServices }] = watch(["syncOptions"]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{autoRedeployServices ? (
|
||||
<GenericFieldLabel label="Auto Redeploy Services">
|
||||
<Badge variant="success">Enabled</Badge>
|
||||
</GenericFieldLabel>
|
||||
) : (
|
||||
<GenericFieldLabel label="Auto Redeploy Services">
|
||||
<Badge variant="danger">Disabled</Badge>
|
||||
</GenericFieldLabel>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const RenderSyncReviewFields = () => {
|
||||
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.Render }>();
|
||||
const serviceName = watch("destinationConfig.serviceName");
|
||||
|
||||
@@ -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 = <RenderSyncReviewFields />;
|
||||
AdditionalSyncOptionsFieldsComponent = <RenderSyncOptionsReviewFields />;
|
||||
break;
|
||||
case SecretSync.Flyio:
|
||||
DestinationFieldsComponent = <FlyioSyncReviewFields />;
|
||||
|
||||
@@ -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", [
|
||||
|
||||
@@ -16,6 +16,10 @@ export type TRenderSync = TRootSecretSync & {
|
||||
name: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
syncOptions: {
|
||||
autoRedeployServices?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export enum RenderSyncScope {
|
||||
|
||||
@@ -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 (
|
||||
<div>
|
||||
{autoRedeployServices ? (
|
||||
<GenericFieldLabel label="Auto Redeploy Services">
|
||||
<Badge variant="success">Enabled</Badge>
|
||||
</GenericFieldLabel>
|
||||
) : (
|
||||
<GenericFieldLabel label="Auto Redeploy Services">
|
||||
<Badge variant="danger">Disabled</Badge>
|
||||
</GenericFieldLabel>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -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) =
|
||||
<AwsSecretsManagerSyncOptionsSection secretSync={secretSync} />
|
||||
);
|
||||
break;
|
||||
case SecretSync.Render:
|
||||
AdditionalSyncOptionsComponent = <RenderSyncOptionsSection secretSync={secretSync} />;
|
||||
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:
|
||||
|
||||
Reference in New Issue
Block a user