mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 20:27:43 +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.",
|
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.",
|
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.`
|
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: {
|
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 = {
|
export const RenderSyncFns = {
|
||||||
syncSecrets: async (secretSync: TRenderSyncWithCredentials, secretMap: TSecretMap) => {
|
syncSecrets: async (secretSync: TRenderSyncWithCredentials, secretMap: TSecretMap) => {
|
||||||
const renderSecrets = await getRenderEnvironmentSecrets(secretSync);
|
const renderSecrets = await getRenderEnvironmentSecrets(secretSync);
|
||||||
@@ -131,6 +153,10 @@ export const RenderSyncFns = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await batchUpdateEnvironmentSecrets(secretSync, finalEnvVars);
|
await batchUpdateEnvironmentSecrets(secretSync, finalEnvVars);
|
||||||
|
|
||||||
|
if (secretSync.syncOptions.autoRedeployServices) {
|
||||||
|
await redeployService(secretSync);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
getSecrets: async (secretSync: TRenderSyncWithCredentials): Promise<TSecretMap> => {
|
getSecrets: async (secretSync: TRenderSyncWithCredentials): Promise<TSecretMap> => {
|
||||||
@@ -151,5 +177,9 @@ export const RenderSyncFns = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
await batchUpdateEnvironmentSecrets(secretSync, finalEnvVars);
|
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 };
|
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),
|
destination: z.literal(SecretSync.Render),
|
||||||
destinationConfig: RenderSyncDestinationConfigSchema
|
destinationConfig: RenderSyncDestinationConfigSchema
|
||||||
});
|
});
|
||||||
|
|
||||||
export const CreateRenderSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
export const CreateRenderSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||||
SecretSync.Render,
|
SecretSync.Render,
|
||||||
RenderSyncOptionsConfig
|
RenderSyncOptionsConfig,
|
||||||
|
RenderSyncOptionsSchema
|
||||||
).extend({
|
).extend({
|
||||||
destinationConfig: RenderSyncDestinationConfigSchema
|
destinationConfig: RenderSyncDestinationConfigSchema
|
||||||
});
|
});
|
||||||
|
|
||||||
export const UpdateRenderSyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
export const UpdateRenderSyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||||
SecretSync.Render,
|
SecretSync.Render,
|
||||||
RenderSyncOptionsConfig
|
RenderSyncOptionsConfig,
|
||||||
|
RenderSyncOptionsSchema
|
||||||
).extend({
|
).extend({
|
||||||
destinationConfig: RenderSyncDestinationConfigSchema.optional()
|
destinationConfig: RenderSyncDestinationConfigSchema.optional()
|
||||||
});
|
});
|
||||||
|
|||||||
+40
@@ -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>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
+4
-1
@@ -14,6 +14,7 @@ import { SecretSync, useSecretSyncOption } from "@app/hooks/api/secretSyncs";
|
|||||||
import { TSecretSyncForm } from "../schemas";
|
import { TSecretSyncForm } from "../schemas";
|
||||||
import { AwsParameterStoreSyncOptionsFields } from "./AwsParameterStoreSyncOptionsFields";
|
import { AwsParameterStoreSyncOptionsFields } from "./AwsParameterStoreSyncOptionsFields";
|
||||||
import { AwsSecretsManagerSyncOptionsFields } from "./AwsSecretsManagerSyncOptionsFields";
|
import { AwsSecretsManagerSyncOptionsFields } from "./AwsSecretsManagerSyncOptionsFields";
|
||||||
|
import { RenderSyncOptionsFields } from "./RenderSyncOptionsFields";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
hideInitialSync?: boolean;
|
hideInitialSync?: boolean;
|
||||||
@@ -38,6 +39,9 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
|
|||||||
case SecretSync.AWSSecretsManager:
|
case SecretSync.AWSSecretsManager:
|
||||||
AdditionalSyncOptionsFieldsComponent = <AwsSecretsManagerSyncOptionsFields />;
|
AdditionalSyncOptionsFieldsComponent = <AwsSecretsManagerSyncOptionsFields />;
|
||||||
break;
|
break;
|
||||||
|
case SecretSync.Render:
|
||||||
|
AdditionalSyncOptionsFieldsComponent = <RenderSyncOptionsFields />;
|
||||||
|
break;
|
||||||
case SecretSync.GitHub:
|
case SecretSync.GitHub:
|
||||||
case SecretSync.GCPSecretManager:
|
case SecretSync.GCPSecretManager:
|
||||||
case SecretSync.AzureKeyVault:
|
case SecretSync.AzureKeyVault:
|
||||||
@@ -54,7 +58,6 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
|
|||||||
case SecretSync.OnePass:
|
case SecretSync.OnePass:
|
||||||
case SecretSync.OCIVault:
|
case SecretSync.OCIVault:
|
||||||
case SecretSync.Heroku:
|
case SecretSync.Heroku:
|
||||||
case SecretSync.Render:
|
|
||||||
case SecretSync.Flyio:
|
case SecretSync.Flyio:
|
||||||
case SecretSync.GitLab:
|
case SecretSync.GitLab:
|
||||||
case SecretSync.CloudflarePages:
|
case SecretSync.CloudflarePages:
|
||||||
|
|||||||
+21
@@ -2,8 +2,29 @@ import { useFormContext } from "react-hook-form";
|
|||||||
|
|
||||||
import { GenericFieldLabel } from "@app/components/secret-syncs";
|
import { GenericFieldLabel } from "@app/components/secret-syncs";
|
||||||
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
|
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
|
||||||
|
import { Badge } from "@app/components/v2";
|
||||||
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
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 = () => {
|
export const RenderSyncReviewFields = () => {
|
||||||
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.Render }>();
|
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.Render }>();
|
||||||
const serviceName = watch("destinationConfig.serviceName");
|
const serviceName = watch("destinationConfig.serviceName");
|
||||||
|
|||||||
+2
-1
@@ -35,7 +35,7 @@ import { HumanitecSyncReviewFields } from "./HumanitecSyncReviewFields";
|
|||||||
import { OCIVaultSyncReviewFields } from "./OCIVaultSyncReviewFields";
|
import { OCIVaultSyncReviewFields } from "./OCIVaultSyncReviewFields";
|
||||||
import { OnePassSyncReviewFields } from "./OnePassSyncReviewFields";
|
import { OnePassSyncReviewFields } from "./OnePassSyncReviewFields";
|
||||||
import { RailwaySyncReviewFields } from "./RailwaySyncReviewFields";
|
import { RailwaySyncReviewFields } from "./RailwaySyncReviewFields";
|
||||||
import { RenderSyncReviewFields } from "./RenderSyncReviewFields";
|
import { RenderSyncOptionsReviewFields, RenderSyncReviewFields } from "./RenderSyncReviewFields";
|
||||||
import { SupabaseSyncReviewFields } from "./SupabaseSyncReviewFields";
|
import { SupabaseSyncReviewFields } from "./SupabaseSyncReviewFields";
|
||||||
import { TeamCitySyncReviewFields } from "./TeamCitySyncReviewFields";
|
import { TeamCitySyncReviewFields } from "./TeamCitySyncReviewFields";
|
||||||
import { TerraformCloudSyncReviewFields } from "./TerraformCloudSyncReviewFields";
|
import { TerraformCloudSyncReviewFields } from "./TerraformCloudSyncReviewFields";
|
||||||
@@ -121,6 +121,7 @@ export const SecretSyncReviewFields = () => {
|
|||||||
break;
|
break;
|
||||||
case SecretSync.Render:
|
case SecretSync.Render:
|
||||||
DestinationFieldsComponent = <RenderSyncReviewFields />;
|
DestinationFieldsComponent = <RenderSyncReviewFields />;
|
||||||
|
AdditionalSyncOptionsFieldsComponent = <RenderSyncOptionsReviewFields />;
|
||||||
break;
|
break;
|
||||||
case SecretSync.Flyio:
|
case SecretSync.Flyio:
|
||||||
DestinationFieldsComponent = <FlyioSyncReviewFields />;
|
DestinationFieldsComponent = <FlyioSyncReviewFields />;
|
||||||
|
|||||||
+5
-1
@@ -4,7 +4,11 @@ import { BaseSecretSyncSchema } from "@app/components/secret-syncs/forms/schemas
|
|||||||
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
import { RenderSyncScope, RenderSyncType } from "@app/hooks/api/secretSyncs/render-sync";
|
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({
|
z.object({
|
||||||
destination: z.literal(SecretSync.Render),
|
destination: z.literal(SecretSync.Render),
|
||||||
destinationConfig: z.discriminatedUnion("scope", [
|
destinationConfig: z.discriminatedUnion("scope", [
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ export type TRenderSync = TRootSecretSync & {
|
|||||||
name: string;
|
name: string;
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
syncOptions: {
|
||||||
|
autoRedeployServices?: boolean;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum RenderSyncScope {
|
export enum RenderSyncScope {
|
||||||
|
|||||||
+27
@@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
+4
-1
@@ -13,6 +13,7 @@ import { SecretSync, TSecretSync } from "@app/hooks/api/secretSyncs";
|
|||||||
|
|
||||||
import { AwsParameterStoreSyncOptionsSection } from "./AwsParameterStoreSyncOptionsSection";
|
import { AwsParameterStoreSyncOptionsSection } from "./AwsParameterStoreSyncOptionsSection";
|
||||||
import { AwsSecretsManagerSyncOptionsSection } from "./AwsSecretsManagerSyncOptionsSection";
|
import { AwsSecretsManagerSyncOptionsSection } from "./AwsSecretsManagerSyncOptionsSection";
|
||||||
|
import { RenderSyncOptionsSection } from "./RenderSyncOptionsSection";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
secretSync: TSecretSync;
|
secretSync: TSecretSync;
|
||||||
@@ -40,6 +41,9 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) =
|
|||||||
<AwsSecretsManagerSyncOptionsSection secretSync={secretSync} />
|
<AwsSecretsManagerSyncOptionsSection secretSync={secretSync} />
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case SecretSync.Render:
|
||||||
|
AdditionalSyncOptionsComponent = <RenderSyncOptionsSection secretSync={secretSync} />;
|
||||||
|
break;
|
||||||
case SecretSync.GitHub:
|
case SecretSync.GitHub:
|
||||||
case SecretSync.GCPSecretManager:
|
case SecretSync.GCPSecretManager:
|
||||||
case SecretSync.AzureKeyVault:
|
case SecretSync.AzureKeyVault:
|
||||||
@@ -56,7 +60,6 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) =
|
|||||||
case SecretSync.OCIVault:
|
case SecretSync.OCIVault:
|
||||||
case SecretSync.OnePass:
|
case SecretSync.OnePass:
|
||||||
case SecretSync.Heroku:
|
case SecretSync.Heroku:
|
||||||
case SecretSync.Render:
|
|
||||||
case SecretSync.Flyio:
|
case SecretSync.Flyio:
|
||||||
case SecretSync.GitLab:
|
case SecretSync.GitLab:
|
||||||
case SecretSync.CloudflarePages:
|
case SecretSync.CloudflarePages:
|
||||||
|
|||||||
Reference in New Issue
Block a user