diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 1f5f5a4ea..42167bce3 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -2384,6 +2384,11 @@ export const SecretSyncs = { }, ONEPASS: { vaultId: "The ID of the 1Password vault to sync secrets to." + }, + RENDER: { + serviceId: "The ID of the Render service to sync secrets to.", + scope: "The Render scope that secrets should be synced to.", + type: "The type of Render resource to sync secrets to." } } }; diff --git a/backend/src/server/routes/v1/app-connection-routers/render-connection-router.ts b/backend/src/server/routes/v1/app-connection-routers/render-connection-router.ts index 2a78eb99b..97cff2cd3 100644 --- a/backend/src/server/routes/v1/app-connection-routers/render-connection-router.ts +++ b/backend/src/server/routes/v1/app-connection-routers/render-connection-router.ts @@ -1,9 +1,14 @@ +import z from "zod"; + +import { readLimit } from "@app/server/config/rateLimiter"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AppConnection } from "@app/services/app-connection/app-connection-enums"; import { CreateRenderConnectionSchema, SanitizedRenderConnectionSchema, UpdateRenderConnectionSchema } from "@app/services/app-connection/render/render-connection-schema"; +import { AuthMode } from "@app/services/auth/auth-type"; import { registerAppConnectionEndpoints } from "./app-connection-endpoints"; @@ -15,4 +20,33 @@ export const registerRenderConnectionRouter = async (server: FastifyZodProvider) createSchema: CreateRenderConnectionSchema, updateSchema: UpdateRenderConnectionSchema }); + + // The below endpoints are not exposed and for Infisical App use + server.route({ + method: "GET", + url: `/:connectionId/services`, + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + connectionId: z.string().uuid() + }), + response: { + 200: z + .object({ + id: z.string(), + name: z.string() + }) + .array() + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const { connectionId } = req.params; + const services = await server.services.appConnection.render.listServices(connectionId, req.permission); + + return services; + } + }); }; diff --git a/backend/src/server/routes/v1/secret-sync-routers/index.ts b/backend/src/server/routes/v1/secret-sync-routers/index.ts index 3f4276726..3188c9d97 100644 --- a/backend/src/server/routes/v1/secret-sync-routers/index.ts +++ b/backend/src/server/routes/v1/secret-sync-routers/index.ts @@ -13,6 +13,7 @@ import { registerGcpSyncRouter } from "./gcp-sync-router"; import { registerGitHubSyncRouter } from "./github-sync-router"; import { registerHCVaultSyncRouter } from "./hc-vault-sync-router"; import { registerHumanitecSyncRouter } from "./humanitec-sync-router"; +import { registerRenderSyncRouter } from "./render-sync-router"; import { registerTeamCitySyncRouter } from "./teamcity-sync-router"; import { registerTerraformCloudSyncRouter } from "./terraform-cloud-sync-router"; import { registerVercelSyncRouter } from "./vercel-sync-router"; @@ -37,5 +38,6 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record + registerSyncSecretsEndpoints({ + destination: SecretSync.Render, + server, + responseSchema: RenderSyncSchema, + createSchema: CreateRenderSyncSchema, + updateSchema: UpdateRenderSyncSchema + }); diff --git a/backend/src/server/routes/v1/secret-sync-routers/secret-sync-router.ts b/backend/src/server/routes/v1/secret-sync-routers/secret-sync-router.ts index 17646e8a1..eb22664ed 100644 --- a/backend/src/server/routes/v1/secret-sync-routers/secret-sync-router.ts +++ b/backend/src/server/routes/v1/secret-sync-routers/secret-sync-router.ts @@ -27,6 +27,7 @@ import { GcpSyncListItemSchema, GcpSyncSchema } from "@app/services/secret-sync/ import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret-sync/github"; import { HCVaultSyncListItemSchema, HCVaultSyncSchema } from "@app/services/secret-sync/hc-vault"; import { HumanitecSyncListItemSchema, HumanitecSyncSchema } from "@app/services/secret-sync/humanitec"; +import { RenderSyncListItemSchema, RenderSyncSchema } from "@app/services/secret-sync/render/render-sync-schemas"; import { TeamCitySyncListItemSchema, TeamCitySyncSchema } from "@app/services/secret-sync/teamcity"; import { TerraformCloudSyncListItemSchema, TerraformCloudSyncSchema } from "@app/services/secret-sync/terraform-cloud"; import { VercelSyncListItemSchema, VercelSyncSchema } from "@app/services/secret-sync/vercel"; @@ -49,7 +50,8 @@ const SecretSyncSchema = z.discriminatedUnion("destination", [ HCVaultSyncSchema, TeamCitySyncSchema, OCIVaultSyncSchema, - OnePassSyncSchema + OnePassSyncSchema, + RenderSyncSchema ]); const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [ @@ -69,7 +71,8 @@ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [ HCVaultSyncListItemSchema, TeamCitySyncListItemSchema, OCIVaultSyncListItemSchema, - OnePassSyncListItemSchema + OnePassSyncListItemSchema, + RenderSyncListItemSchema ]); export const registerSecretSyncRouter = async (server: FastifyZodProvider) => { diff --git a/backend/src/services/app-connection/app-connection-service.ts b/backend/src/services/app-connection/app-connection-service.ts index 2e379a3bd..33c7fa07f 100644 --- a/backend/src/services/app-connection/app-connection-service.ts +++ b/backend/src/services/app-connection/app-connection-service.ts @@ -63,6 +63,7 @@ import { ValidateMsSqlConnectionCredentialsSchema } from "./mssql"; import { ValidateMySqlConnectionCredentialsSchema } from "./mysql"; import { ValidatePostgresConnectionCredentialsSchema } from "./postgres"; import { ValidateRenderConnectionCredentialsSchema } from "./render/render-connection-schema"; +import { renderConnectionService } from "./render/render-connection-service"; import { ValidateTeamCityConnectionCredentialsSchema } from "./teamcity"; import { teamcityConnectionService } from "./teamcity/teamcity-connection-service"; import { ValidateTerraformCloudConnectionCredentialsSchema } from "./terraform-cloud"; @@ -511,6 +512,7 @@ export const appConnectionServiceFactory = ({ windmill: windmillConnectionService(connectAppConnectionById), teamcity: teamcityConnectionService(connectAppConnectionById), oci: ociConnectionService(connectAppConnectionById, licenseService), - onepass: onePassConnectionService(connectAppConnectionById) + onepass: onePassConnectionService(connectAppConnectionById), + render: renderConnectionService(connectAppConnectionById) }; }; diff --git a/backend/src/services/app-connection/render/render-connection-fns.ts b/backend/src/services/app-connection/render/render-connection-fns.ts index 5792cec51..bf85b9b71 100644 --- a/backend/src/services/app-connection/render/render-connection-fns.ts +++ b/backend/src/services/app-connection/render/render-connection-fns.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-await-in-loop */ import { AxiosError } from "axios"; import { request } from "@app/lib/config/request"; @@ -6,7 +7,12 @@ import { IntegrationUrls } from "@app/services/integration-auth/integration-list import { AppConnection } from "../app-connection-enums"; import { RenderConnectionMethod } from "./render-connection-enums"; -import { TRenderConnectionConfig } from "./render-connection-types"; +import { + TRawRenderService, + TRenderConnection, + TRenderConnectionConfig, + TRenderService +} from "./render-connection-types"; export const getRenderConnectionListItem = () => { return { @@ -16,6 +22,48 @@ export const getRenderConnectionListItem = () => { }; }; +export const listRenderServices = async (appConnection: TRenderConnection): Promise => { + const { + credentials: { apiKey } + } = appConnection; + + const services: TRenderService[] = []; + let hasMorePages = true; + const perPage = 100; + let cursor; + + while (hasMorePages) { + const res: TRawRenderService[] = ( + await request.get(`${IntegrationUrls.RENDER_API_URL}/v1/services`, { + params: new URLSearchParams({ + ...(cursor ? { cursor: String(cursor) } : {}), + limit: String(perPage) + }), + headers: { + Authorization: `Bearer ${apiKey}`, + Accept: "application/json", + "Accept-Encoding": "application/json" + } + }) + ).data; + + res.forEach((item) => { + services.push({ + name: item.service.name, + id: item.service.id + }); + }); + + if (res.length < perPage) { + hasMorePages = false; + } else { + cursor = res[res.length - 1].cursor; + } + } + + return services; +}; + export const validateRenderConnectionCredentials = async (config: TRenderConnectionConfig) => { const { credentials: inputCredentials } = config; diff --git a/backend/src/services/app-connection/render/render-connection-service.ts b/backend/src/services/app-connection/render/render-connection-service.ts new file mode 100644 index 000000000..371790bcb --- /dev/null +++ b/backend/src/services/app-connection/render/render-connection-service.ts @@ -0,0 +1,30 @@ +import { logger } from "@app/lib/logger"; +import { OrgServiceActor } from "@app/lib/types"; + +import { AppConnection } from "../app-connection-enums"; +import { listRenderServices } from "./render-connection-fns"; +import { TRenderConnection } from "./render-connection-types"; + +type TGetAppConnectionFunc = ( + app: AppConnection, + connectionId: string, + actor: OrgServiceActor +) => Promise; + +export const renderConnectionService = (getAppConnection: TGetAppConnectionFunc) => { + const listServices = async (connectionId: string, actor: OrgServiceActor) => { + const appConnection = await getAppConnection(AppConnection.Render, connectionId, actor); + try { + const services = await listRenderServices(appConnection); + + return services; + } catch (error) { + logger.error(error, "Failed to list services for Render connection"); + return []; + } + }; + + return { + listServices + }; +}; diff --git a/backend/src/services/app-connection/render/render-connection-types.ts b/backend/src/services/app-connection/render/render-connection-types.ts index 025259904..0902472e5 100644 --- a/backend/src/services/app-connection/render/render-connection-types.ts +++ b/backend/src/services/app-connection/render/render-connection-types.ts @@ -20,3 +20,16 @@ export type TValidateRenderConnectionCredentialsSchema = typeof ValidateRenderCo export type TRenderConnectionConfig = DiscriminativePick & { orgId: string; }; + +export type TRenderService = { + name: string; + id: string; +}; + +export type TRawRenderService = { + cursor: string; + service: { + id: string; + name: string; + }; +}; diff --git a/backend/src/services/secret-sync/render/index.ts b/backend/src/services/secret-sync/render/index.ts new file mode 100644 index 000000000..7e8ddd412 --- /dev/null +++ b/backend/src/services/secret-sync/render/index.ts @@ -0,0 +1,4 @@ +export * from "./render-sync-constants"; +export * from "./render-sync-fns"; +export * from "./render-sync-schemas"; +export * from "./render-sync-types"; diff --git a/backend/src/services/secret-sync/render/render-sync-constants.ts b/backend/src/services/secret-sync/render/render-sync-constants.ts new file mode 100644 index 000000000..0246a95d9 --- /dev/null +++ b/backend/src/services/secret-sync/render/render-sync-constants.ts @@ -0,0 +1,10 @@ +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; +import { TSecretSyncListItem } from "@app/services/secret-sync/secret-sync-types"; + +export const RENDER_SYNC_LIST_OPTION: TSecretSyncListItem = { + name: "Render", + destination: SecretSync.Render, + connection: AppConnection.Render, + canImportSecrets: true +}; diff --git a/backend/src/services/secret-sync/render/render-sync-enums.ts b/backend/src/services/secret-sync/render/render-sync-enums.ts new file mode 100644 index 000000000..dc0af3b91 --- /dev/null +++ b/backend/src/services/secret-sync/render/render-sync-enums.ts @@ -0,0 +1,8 @@ +export enum RenderSyncScope { + Service = "service" +} + +export enum RenderSyncType { + Env = "env", + File = "file" +} diff --git a/backend/src/services/secret-sync/render/render-sync-fns.ts b/backend/src/services/secret-sync/render/render-sync-fns.ts new file mode 100644 index 000000000..adfc442d8 --- /dev/null +++ b/backend/src/services/secret-sync/render/render-sync-fns.ts @@ -0,0 +1,126 @@ +/* eslint-disable no-await-in-loop */ +import { request } from "@app/lib/config/request"; +import { IntegrationUrls } from "@app/services/integration-auth/integration-list"; +import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns"; +import { TSecretMap } from "@app/services/secret-sync/secret-sync-types"; + +import { TRenderSecret, TRenderSyncWithCredentials } from "./render-sync-types"; + +const getRenderEnvironmentSecrets = async (secretSync: TRenderSyncWithCredentials) => { + const { + destinationConfig, + connection: { + credentials: { apiKey } + } + } = secretSync; + + const baseUrl = `${IntegrationUrls.RENDER_API_URL}/v1/services/${destinationConfig.serviceId}/env-vars`; + const allSecrets: TRenderSecret[] = []; + let cursor: string | undefined; + + do { + const url = cursor ? `${baseUrl}?cursor=${cursor}` : baseUrl; + const { data } = await request.get< + { + envVar: { + key: string; + value: string; + }; + cursor: string; + }[] + >(url, { + headers: { + Authorization: `Bearer ${apiKey}`, + Accept: "application/json" + } + }); + + const secrets = data.map((item) => ({ + key: item.envVar.key, + value: item.envVar.value + })); + + allSecrets.push(...secrets); + + cursor = data[data.length - 1]?.cursor; + } while (cursor); + + return allSecrets; +}; + +const putEnvironmentSecret = async (secretSync: TRenderSyncWithCredentials, secretMap: TSecretMap, key: string) => { + const { + destinationConfig, + connection: { + credentials: { apiKey } + } + } = secretSync; + + await request.put( + `${IntegrationUrls.RENDER_API_URL}/v1/services/${destinationConfig.serviceId}/env-vars/${key}`, + { + key, + value: secretMap[key].value + }, + { + headers: { + Authorization: `Bearer ${apiKey}`, + Accept: "application/json" + } + } + ); +}; + +const deleteEnvironmentSecret = async (secretSync: TRenderSyncWithCredentials, secret: TRenderSecret) => { + const { + destinationConfig, + connection: { + credentials: { apiKey } + } + } = secretSync; + + await request.delete( + `${IntegrationUrls.RENDER_API_URL}/v1/services/${destinationConfig.serviceId}/env-vars/${secret.key}`, + { + headers: { + Authorization: `Bearer ${apiKey}`, + Accept: "application/json" + } + } + ); +}; + +export const RenderSyncFns = { + syncSecrets: async (secretSync: TRenderSyncWithCredentials, secretMap: TSecretMap) => { + const renderSecrets = await getRenderEnvironmentSecrets(secretSync); + for await (const key of Object.keys(secretMap)) { + await putEnvironmentSecret(secretSync, secretMap, key); + } + + if (secretSync.syncOptions.disableSecretDeletion) return; + + for await (const renderSecret of renderSecrets) { + if (!matchesSchema(renderSecret.key, secretSync.environment?.slug || "", secretSync.syncOptions.keySchema)) + // eslint-disable-next-line no-continue + continue; + + if (!secretMap[renderSecret.key]) { + await deleteEnvironmentSecret(secretSync, renderSecret); + } + } + }, + getSecrets: async (secretSync: TRenderSyncWithCredentials): Promise => { + const renderSecrets = await getRenderEnvironmentSecrets(secretSync); + return Object.fromEntries(renderSecrets.map((secret) => [secret.key, { value: secret.value ?? "" }])); + }, + + removeSecrets: async (secretSync: TRenderSyncWithCredentials, secretMap: TSecretMap) => { + const encryptedSecrets = await getRenderEnvironmentSecrets(secretSync); + + for await (const encryptedSecret of encryptedSecrets) { + if (encryptedSecret.key in secretMap) { + await deleteEnvironmentSecret(secretSync, encryptedSecret); + } + } + } +}; diff --git a/backend/src/services/secret-sync/render/render-sync-schemas.ts b/backend/src/services/secret-sync/render/render-sync-schemas.ts new file mode 100644 index 000000000..77414c17c --- /dev/null +++ b/backend/src/services/secret-sync/render/render-sync-schemas.ts @@ -0,0 +1,49 @@ +import { z } from "zod"; + +import { SecretSyncs } from "@app/lib/api-docs"; +import { AppConnection } from "@app/services/app-connection/app-connection-enums"; +import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; +import { + BaseSecretSyncSchema, + GenericCreateSecretSyncFieldsSchema, + GenericUpdateSecretSyncFieldsSchema +} from "@app/services/secret-sync/secret-sync-schemas"; +import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; + +import { RenderSyncScope, RenderSyncType } from "./render-sync-enums"; + +const RenderSyncDestinationConfigSchema = z.discriminatedUnion("scope", [ + z.object({ + scope: z.literal(RenderSyncScope.Service).describe(SecretSyncs.DESTINATION_CONFIG.RENDER.scope), + serviceId: z.string().min(1, "Service ID is required").describe(SecretSyncs.DESTINATION_CONFIG.RENDER.serviceId), + type: z.nativeEnum(RenderSyncType).describe(SecretSyncs.DESTINATION_CONFIG.RENDER.type) + }) +]); + +const RenderSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true }; + +export const RenderSyncSchema = BaseSecretSyncSchema(SecretSync.Render, RenderSyncOptionsConfig).extend({ + destination: z.literal(SecretSync.Render), + destinationConfig: RenderSyncDestinationConfigSchema +}); + +export const CreateRenderSyncSchema = GenericCreateSecretSyncFieldsSchema( + SecretSync.Render, + RenderSyncOptionsConfig +).extend({ + destinationConfig: RenderSyncDestinationConfigSchema +}); + +export const UpdateRenderSyncSchema = GenericUpdateSecretSyncFieldsSchema( + SecretSync.Render, + RenderSyncOptionsConfig +).extend({ + destinationConfig: RenderSyncDestinationConfigSchema.optional() +}); + +export const RenderSyncListItemSchema = z.object({ + name: z.literal("Render"), + connection: z.literal(AppConnection.Render), + destination: z.literal(SecretSync.Render), + canImportSecrets: z.literal(true) +}); diff --git a/backend/src/services/secret-sync/render/render-sync-types.ts b/backend/src/services/secret-sync/render/render-sync-types.ts new file mode 100644 index 000000000..22d479384 --- /dev/null +++ b/backend/src/services/secret-sync/render/render-sync-types.ts @@ -0,0 +1,20 @@ +import z from "zod"; + +import { TRenderConnection } from "@app/services/app-connection/render/render-connection-types"; + +import { CreateRenderSyncSchema, RenderSyncListItemSchema, RenderSyncSchema } from "./render-sync-schemas"; + +export type TRenderSyncListItem = z.infer; + +export type TRenderSync = z.infer; + +export type TRenderSyncInput = z.infer; + +export type TRenderSyncWithCredentials = TRenderSync & { + connection: TRenderConnection; +}; + +export type TRenderSecret = { + key: string; + value: string; +}; diff --git a/backend/src/services/secret-sync/secret-sync-enums.ts b/backend/src/services/secret-sync/secret-sync-enums.ts index 3cf940459..609b5f639 100644 --- a/backend/src/services/secret-sync/secret-sync-enums.ts +++ b/backend/src/services/secret-sync/secret-sync-enums.ts @@ -15,7 +15,8 @@ export enum SecretSync { HCVault = "hashicorp-vault", TeamCity = "teamcity", OCIVault = "oci-vault", - OnePass = "1password" + OnePass = "1password", + Render = "render" } export enum SecretSyncInitialSyncBehavior { diff --git a/backend/src/services/secret-sync/secret-sync-fns.ts b/backend/src/services/secret-sync/secret-sync-fns.ts index bcda32967..c4af17750 100644 --- a/backend/src/services/secret-sync/secret-sync-fns.ts +++ b/backend/src/services/secret-sync/secret-sync-fns.ts @@ -34,6 +34,7 @@ import { GcpSyncFns } from "./gcp/gcp-sync-fns"; import { HC_VAULT_SYNC_LIST_OPTION, HCVaultSyncFns } from "./hc-vault"; import { HUMANITEC_SYNC_LIST_OPTION } from "./humanitec"; import { HumanitecSyncFns } from "./humanitec/humanitec-sync-fns"; +import { RENDER_SYNC_LIST_OPTION, RenderSyncFns } from "./render"; import { SECRET_SYNC_PLAN_MAP } from "./secret-sync-maps"; import { TEAMCITY_SYNC_LIST_OPTION, TeamCitySyncFns } from "./teamcity"; import { TERRAFORM_CLOUD_SYNC_LIST_OPTION, TerraformCloudSyncFns } from "./terraform-cloud"; @@ -57,7 +58,8 @@ const SECRET_SYNC_LIST_OPTIONS: Record = { [SecretSync.HCVault]: HC_VAULT_SYNC_LIST_OPTION, [SecretSync.TeamCity]: TEAMCITY_SYNC_LIST_OPTION, [SecretSync.OCIVault]: OCI_VAULT_SYNC_LIST_OPTION, - [SecretSync.OnePass]: ONEPASS_SYNC_LIST_OPTION + [SecretSync.OnePass]: ONEPASS_SYNC_LIST_OPTION, + [SecretSync.Render]: RENDER_SYNC_LIST_OPTION }; export const listSecretSyncOptions = () => { @@ -215,6 +217,8 @@ export const SecretSyncFns = { return OCIVaultSyncFns.syncSecrets(secretSync, schemaSecretMap); case SecretSync.OnePass: return OnePassSyncFns.syncSecrets(secretSync, schemaSecretMap); + case SecretSync.Render: + return RenderSyncFns.syncSecrets(secretSync, schemaSecretMap); default: throw new Error( `Unhandled sync destination for sync secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}` @@ -292,6 +296,9 @@ export const SecretSyncFns = { case SecretSync.OnePass: secretMap = await OnePassSyncFns.getSecrets(secretSync); break; + case SecretSync.Render: + secretMap = await RenderSyncFns.getSecrets(secretSync); + break; default: throw new Error( `Unhandled sync destination for get secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}` @@ -359,6 +366,8 @@ export const SecretSyncFns = { return OCIVaultSyncFns.removeSecrets(secretSync, schemaSecretMap); case SecretSync.OnePass: return OnePassSyncFns.removeSecrets(secretSync, schemaSecretMap); + case SecretSync.Render: + return RenderSyncFns.removeSecrets(secretSync, schemaSecretMap); default: throw new Error( `Unhandled sync destination for remove secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}` diff --git a/backend/src/services/secret-sync/secret-sync-maps.ts b/backend/src/services/secret-sync/secret-sync-maps.ts index dd329734a..9112ab0d2 100644 --- a/backend/src/services/secret-sync/secret-sync-maps.ts +++ b/backend/src/services/secret-sync/secret-sync-maps.ts @@ -18,7 +18,8 @@ export const SECRET_SYNC_NAME_MAP: Record = { [SecretSync.HCVault]: "Hashicorp Vault", [SecretSync.TeamCity]: "TeamCity", [SecretSync.OCIVault]: "OCI Vault", - [SecretSync.OnePass]: "1Password" + [SecretSync.OnePass]: "1Password", + [SecretSync.Render]: "Render" }; export const SECRET_SYNC_CONNECTION_MAP: Record = { @@ -38,7 +39,8 @@ export const SECRET_SYNC_CONNECTION_MAP: Record = { [SecretSync.HCVault]: AppConnection.HCVault, [SecretSync.TeamCity]: AppConnection.TeamCity, [SecretSync.OCIVault]: AppConnection.OCI, - [SecretSync.OnePass]: AppConnection.OnePass + [SecretSync.OnePass]: AppConnection.OnePass, + [SecretSync.Render]: AppConnection.Render }; export const SECRET_SYNC_PLAN_MAP: Record = { @@ -58,5 +60,6 @@ export const SECRET_SYNC_PLAN_MAP: Record = { [SecretSync.HCVault]: SecretSyncPlanType.Regular, [SecretSync.TeamCity]: SecretSyncPlanType.Regular, [SecretSync.OCIVault]: SecretSyncPlanType.Enterprise, - [SecretSync.OnePass]: SecretSyncPlanType.Regular + [SecretSync.OnePass]: SecretSyncPlanType.Regular, + [SecretSync.Render]: SecretSyncPlanType.Regular }; diff --git a/backend/src/services/secret-sync/secret-sync-types.ts b/backend/src/services/secret-sync/secret-sync-types.ts index ff355e5e1..2fdc7c50e 100644 --- a/backend/src/services/secret-sync/secret-sync-types.ts +++ b/backend/src/services/secret-sync/secret-sync-types.ts @@ -85,6 +85,12 @@ import { THumanitecSyncListItem, THumanitecSyncWithCredentials } from "./humanitec"; +import { + TRenderSync, + TRenderSyncInput, + TRenderSyncListItem, + TRenderSyncWithCredentials +} from "./render/render-sync-types"; import { TTeamCitySync, TTeamCitySyncInput, @@ -116,7 +122,8 @@ export type TSecretSync = | THCVaultSync | TTeamCitySync | TOCIVaultSync - | TOnePassSync; + | TOnePassSync + | TRenderSync; export type TSecretSyncWithCredentials = | TAwsParameterStoreSyncWithCredentials @@ -135,7 +142,8 @@ export type TSecretSyncWithCredentials = | THCVaultSyncWithCredentials | TTeamCitySyncWithCredentials | TOCIVaultSyncWithCredentials - | TOnePassSyncWithCredentials; + | TOnePassSyncWithCredentials + | TRenderSyncWithCredentials; export type TSecretSyncInput = | TAwsParameterStoreSyncInput @@ -154,7 +162,8 @@ export type TSecretSyncInput = | THCVaultSyncInput | TTeamCitySyncInput | TOCIVaultSyncInput - | TOnePassSyncInput; + | TOnePassSyncInput + | TRenderSyncInput; export type TSecretSyncListItem = | TAwsParameterStoreSyncListItem @@ -173,7 +182,8 @@ export type TSecretSyncListItem = | THCVaultSyncListItem | TTeamCitySyncListItem | TOCIVaultSyncListItem - | TOnePassSyncListItem; + | TOnePassSyncListItem + | TRenderSyncListItem; export type TSyncOptionsConfig = { canImportSecrets: boolean; diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/RenderSyncFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/RenderSyncFields.tsx new file mode 100644 index 000000000..b5cb407cb --- /dev/null +++ b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/RenderSyncFields.tsx @@ -0,0 +1,112 @@ +import { Controller, useFormContext, useWatch } from "react-hook-form"; +import { SingleValue } from "react-select"; + +import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField"; +import { FilterableSelect, FormControl, Select, SelectItem } from "@app/components/v2"; +import { RENDER_SYNC_SCOPES } from "@app/helpers/secretSyncs"; +import { + TRenderService, + useRenderConnectionListServices +} from "@app/hooks/api/appConnections/render"; +import { SecretSync } from "@app/hooks/api/secretSyncs"; +import { RenderSyncScope, RenderSyncType } from "@app/hooks/api/secretSyncs/render-sync"; + +import { TSecretSyncForm } from "../schemas"; + +export const RenderSyncFields = () => { + const { control, setValue } = useFormContext< + TSecretSyncForm & { destination: SecretSync.Render } + >(); + + const connectionId = useWatch({ name: "connection.id", control }); + + const { data: services = [], isPending: isServicesPending } = useRenderConnectionListServices( + connectionId, + { + enabled: Boolean(connectionId) + } + ); + + return ( + <> + { + setValue("destinationConfig.serviceId", ""); + setValue("destinationConfig.type", RenderSyncType.Env); + setValue("destinationConfig.scope", RenderSyncScope.Service); + }} + /> + ( + +

+ Specify how Infisical should manage secrets from Render. The following options are + available: +

+
    + {Object.values(RENDER_SYNC_SCOPES).map(({ name, description }) => { + return ( +
  • +

    + {name}: {description} +

    +
  • + ); + })} +
+ + } + > + +
+ )} + /> + ( + + service.id === value) ?? []) : []} + onChange={(option) => { + onChange((option as SingleValue)?.id ?? null); + setValue( + "destinationConfig.serviceName", + (option as SingleValue)?.name ?? "" + ); + }} + options={services} + placeholder="Select a service..." + getOptionLabel={(option) => option.name} + getOptionValue={(option) => option.id.toString()} + /> + + )} + /> + + ); +}; diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/SecretSyncDestinationFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/SecretSyncDestinationFields.tsx index 71405c448..61b94aec4 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/SecretSyncDestinationFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/SecretSyncDestinationFields.tsx @@ -16,6 +16,7 @@ import { GitHubSyncFields } from "./GitHubSyncFields"; import { HCVaultSyncFields } from "./HCVaultSyncFields"; import { HumanitecSyncFields } from "./HumanitecSyncFields"; import { OCIVaultSyncFields } from "./OCIVaultSyncFields"; +import { RenderSyncFields } from "./RenderSyncFields"; import { TeamCitySyncFields } from "./TeamCitySyncFields"; import { TerraformCloudSyncFields } from "./TerraformCloudSyncFields"; import { VercelSyncFields } from "./VercelSyncFields"; @@ -61,6 +62,8 @@ export const SecretSyncDestinationFields = () => { return ; case SecretSync.OnePass: return ; + case SecretSync.Render: + return ; default: throw new Error(`Unhandled Destination Config Field: ${destination}`); } diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx index 0f6382edc..a1cec91de 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx @@ -52,6 +52,7 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => { case SecretSync.TeamCity: case SecretSync.OnePass: case SecretSync.OCIVault: + case SecretSync.Render: AdditionalSyncOptionsFieldsComponent = null; break; default: diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/RenderSyncReviewFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/RenderSyncReviewFields.tsx new file mode 100644 index 000000000..becc46c1d --- /dev/null +++ b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/RenderSyncReviewFields.tsx @@ -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 RenderSyncReviewFields = () => { + const { watch } = useFormContext(); + const serviceName = watch("destinationConfig.serviceName"); + const scope = watch("destinationConfig.scope"); + + return ( + <> + {scope} + {serviceName} + + ); +}; diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx index 854555ffb..7e678c240 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx @@ -26,6 +26,7 @@ import { HCVaultSyncReviewFields } from "./HCVaultSyncReviewFields"; import { HumanitecSyncReviewFields } from "./HumanitecSyncReviewFields"; import { OCIVaultSyncReviewFields } from "./OCIVaultSyncReviewFields"; import { OnePassSyncReviewFields } from "./OnePassSyncReviewFields"; +import { RenderSyncReviewFields } from "./RenderSyncReviewFields"; import { TeamCitySyncReviewFields } from "./TeamCitySyncReviewFields"; import { TerraformCloudSyncReviewFields } from "./TerraformCloudSyncReviewFields"; import { VercelSyncReviewFields } from "./VercelSyncReviewFields"; @@ -104,6 +105,9 @@ export const SecretSyncReviewFields = () => { case SecretSync.OnePass: DestinationFieldsComponent = ; break; + case SecretSync.Render: + DestinationFieldsComponent = ; + break; default: throw new Error(`Unhandled Destination Review Fields: ${destination}`); } 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 new file mode 100644 index 000000000..16b213421 --- /dev/null +++ b/frontend/src/components/secret-syncs/forms/schemas/render-sync-destination-schema.ts @@ -0,0 +1,19 @@ +import { z } from "zod"; + +import { BaseSecretSyncSchema } from "@app/components/secret-syncs/forms/schemas/base-secret-sync-schema"; +import { SecretSync } from "@app/hooks/api/secretSyncs"; +import { RenderSyncScope, RenderSyncType } from "@app/hooks/api/secretSyncs/render-sync"; + +export const RenderSyncDestinationSchema = BaseSecretSyncSchema().merge( + z.object({ + destination: z.literal(SecretSync.Render), + destinationConfig: z.discriminatedUnion("scope", [ + z.object({ + scope: z.literal(RenderSyncScope.Service), + serviceId: z.string().trim().min(1, "Service is required"), + serviceName: z.string().trim().optional(), + type: z.nativeEnum(RenderSyncType) + }) + ]) + }) +); diff --git a/frontend/src/components/secret-syncs/forms/schemas/secret-sync-schema.ts b/frontend/src/components/secret-syncs/forms/schemas/secret-sync-schema.ts index 5dfb643b4..511075ce8 100644 --- a/frontend/src/components/secret-syncs/forms/schemas/secret-sync-schema.ts +++ b/frontend/src/components/secret-syncs/forms/schemas/secret-sync-schema.ts @@ -13,6 +13,7 @@ import { GitHubSyncDestinationSchema } from "./github-sync-destination-schema"; import { HCVaultSyncDestinationSchema } from "./hc-vault-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"; import { TeamCitySyncDestinationSchema } from "./teamcity-sync-destination-schema"; import { TerraformCloudSyncDestinationSchema } from "./terraform-cloud-destination-schema"; import { VercelSyncDestinationSchema } from "./vercel-sync-destination-schema"; @@ -35,7 +36,8 @@ const SecretSyncUnionSchema = z.discriminatedUnion("destination", [ HCVaultSyncDestinationSchema, TeamCitySyncDestinationSchema, OCIVaultSyncDestinationSchema, - OnePassSyncDestinationSchema + OnePassSyncDestinationSchema, + RenderSyncDestinationSchema ]); export const SecretSyncFormSchema = SecretSyncUnionSchema; diff --git a/frontend/src/helpers/secretSyncs.ts b/frontend/src/helpers/secretSyncs.ts index 98a519e33..ecebec0cf 100644 --- a/frontend/src/helpers/secretSyncs.ts +++ b/frontend/src/helpers/secretSyncs.ts @@ -4,6 +4,7 @@ import { SecretSyncImportBehavior, SecretSyncInitialSyncBehavior } from "@app/hooks/api/secretSyncs"; +import { RenderSyncScope } from "@app/hooks/api/secretSyncs/render-sync"; import { GcpSyncScope } from "@app/hooks/api/secretSyncs/types/gcp-sync"; import { HumanitecSyncScope } from "@app/hooks/api/secretSyncs/types/humanitec-sync"; @@ -60,6 +61,10 @@ export const SECRET_SYNC_MAP: Record = { [SecretSync.HCVault]: AppConnection.HCVault, [SecretSync.TeamCity]: AppConnection.TeamCity, [SecretSync.OCIVault]: AppConnection.OCI, - [SecretSync.OnePass]: AppConnection.OnePass + [SecretSync.OnePass]: AppConnection.OnePass, + [SecretSync.Render]: AppConnection.Render }; export const SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP: Record< @@ -141,3 +147,10 @@ export const GCP_SYNC_SCOPES: Record = { + [RenderSyncScope.Service]: { + name: "Service", + description: "Infisical will sync secrets to the specified Render service." + } +}; diff --git a/frontend/src/hooks/api/appConnections/render/index.ts b/frontend/src/hooks/api/appConnections/render/index.ts new file mode 100644 index 000000000..2c1906d36 --- /dev/null +++ b/frontend/src/hooks/api/appConnections/render/index.ts @@ -0,0 +1,2 @@ +export * from "./queries"; +export * from "./types"; diff --git a/frontend/src/hooks/api/appConnections/render/queries.tsx b/frontend/src/hooks/api/appConnections/render/queries.tsx new file mode 100644 index 000000000..728c46bd9 --- /dev/null +++ b/frontend/src/hooks/api/appConnections/render/queries.tsx @@ -0,0 +1,37 @@ +import { useQuery, UseQueryOptions } from "@tanstack/react-query"; + +import { apiRequest } from "@app/config/request"; + +import { appConnectionKeys } from "../queries"; +import { TRenderService } from "./types"; + +const renderConnectionKeys = { + all: [...appConnectionKeys.all, "render"] as const, + listServices: (connectionId: string) => + [...renderConnectionKeys.all, "services", connectionId] as const +}; + +export const useRenderConnectionListServices = ( + connectionId: string, + options?: Omit< + UseQueryOptions< + TRenderService[], + unknown, + TRenderService[], + ReturnType + >, + "queryKey" | "queryFn" + > +) => { + return useQuery({ + queryKey: renderConnectionKeys.listServices(connectionId), + queryFn: async () => { + const { data } = await apiRequest.get( + `/api/v1/app-connections/render/${connectionId}/services` + ); + + return data; + }, + ...options + }); +}; diff --git a/frontend/src/hooks/api/appConnections/render/types.ts b/frontend/src/hooks/api/appConnections/render/types.ts new file mode 100644 index 000000000..ec51adb78 --- /dev/null +++ b/frontend/src/hooks/api/appConnections/render/types.ts @@ -0,0 +1,4 @@ +export type TRenderService = { + id: string; + name: string; +}; diff --git a/frontend/src/hooks/api/secretSyncs/enums.ts b/frontend/src/hooks/api/secretSyncs/enums.ts index f381b3fca..f7829d8e8 100644 --- a/frontend/src/hooks/api/secretSyncs/enums.ts +++ b/frontend/src/hooks/api/secretSyncs/enums.ts @@ -15,7 +15,8 @@ export enum SecretSync { HCVault = "hashicorp-vault", TeamCity = "teamcity", OCIVault = "oci-vault", - OnePass = "1password" + OnePass = "1password", + Render = "render" } export enum SecretSyncStatus { diff --git a/frontend/src/hooks/api/secretSyncs/render-sync.ts b/frontend/src/hooks/api/secretSyncs/render-sync.ts new file mode 100644 index 000000000..ecac7d077 --- /dev/null +++ b/frontend/src/hooks/api/secretSyncs/render-sync.ts @@ -0,0 +1,28 @@ +import { AppConnection } from "@app/hooks/api/appConnections/enums"; +import { SecretSync } from "@app/hooks/api/secretSyncs"; +import { TRootSecretSync } from "@app/hooks/api/secretSyncs/types/root-sync"; + +export type TRenderSync = TRootSecretSync & { + destination: SecretSync.Render; + destinationConfig: { + scope: RenderSyncScope.Service; + type: RenderSyncType; + serviceId: string; + serviceName?: string; + }; + + connection: { + app: AppConnection.Render; + name: string; + id: string; + }; +}; + +export enum RenderSyncScope { + Service = "service" +} + +export enum RenderSyncType { + Env = "env", + File = "file" +} diff --git a/frontend/src/hooks/api/secretSyncs/types/index.ts b/frontend/src/hooks/api/secretSyncs/types/index.ts index 4f447f9df..3abc217ca 100644 --- a/frontend/src/hooks/api/secretSyncs/types/index.ts +++ b/frontend/src/hooks/api/secretSyncs/types/index.ts @@ -1,6 +1,7 @@ import { SecretSync, SecretSyncImportBehavior } from "@app/hooks/api/secretSyncs"; import { DiscriminativePick } from "@app/types"; +import { TRenderSync } from "../render-sync"; import { TOnePassSync } from "./1password-sync"; import { TAwsParameterStoreSync } from "./aws-parameter-store-sync"; import { TAwsSecretsManagerSync } from "./aws-secrets-manager-sync"; @@ -43,7 +44,8 @@ export type TSecretSync = | THCVaultSync | TTeamCitySync | TOCIVaultSync - | TOnePassSync; + | TOnePassSync + | TRenderSync; export type TListSecretSyncs = { secretSyncs: TSecretSync[] }; diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/RenderSyncDestinationCol.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/RenderSyncDestinationCol.tsx new file mode 100644 index 000000000..43e9e35d2 --- /dev/null +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/RenderSyncDestinationCol.tsx @@ -0,0 +1,29 @@ +import { useRenderConnectionListServices } from "@app/hooks/api/appConnections/render"; +import { TRenderSync } from "@app/hooks/api/secretSyncs/render-sync"; + +import { getSecretSyncDestinationColValues } from "../helpers"; +import { SecretSyncTableCell } from "../SecretSyncTableCell"; + +type Props = { + secretSync: TRenderSync; +}; + +export const RenderSyncDestinationCol = ({ secretSync }: Props) => { + const { data: services = [], isPending } = useRenderConnectionListServices( + secretSync.connectionId + ); + + const { primaryText, secondaryText } = getSecretSyncDestinationColValues({ + ...secretSync, + destinationConfig: { + ...secretSync.destinationConfig, + serviceName: services.find((s) => s.id === secretSync.destinationConfig.serviceId)?.name + } + }); + + if (isPending) { + return ; + } + + return ; +}; diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/SecretSyncDestinationCol.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/SecretSyncDestinationCol.tsx index f342c361c..5e0768599 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/SecretSyncDestinationCol.tsx +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/SecretSyncDestinationCol.tsx @@ -13,6 +13,7 @@ import { GitHubSyncDestinationCol } from "./GitHubSyncDestinationCol"; import { HCVaultSyncDestinationCol } from "./HCVaultSyncDestinationCol"; import { HumanitecSyncDestinationCol } from "./HumanitecSyncDestinationCol"; import { OCIVaultSyncDestinationCol } from "./OCIVaultSyncDestinationCol"; +import { RenderSyncDestinationCol } from "./RenderSyncDestinationCol"; import { TeamCitySyncDestinationCol } from "./TeamCitySyncDestinationCol"; import { TerraformCloudSyncDestinationCol } from "./TerraformCloudSyncDestinationCol"; import { VercelSyncDestinationCol } from "./VercelSyncDestinationCol"; @@ -58,6 +59,8 @@ export const SecretSyncDestinationCol = ({ secretSync }: Props) => { return ; case SecretSync.AzureDevOps: return ; + case SecretSync.Render: + return ; default: throw new Error( `Unhandled Secret Sync Destination Col: ${(secretSync as TSecretSync).destination}` diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/helpers/index.ts b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/helpers/index.ts index 3c1056382..c4e1c8810 100644 --- a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/helpers/index.ts +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/helpers/index.ts @@ -116,6 +116,10 @@ export const getSecretSyncDestinationColValues = (secretSync: TSecretSync) => { primaryText = destinationConfig.devopsProjectName; secondaryText = destinationConfig.devopsProjectId; break; + case SecretSync.Render: + primaryText = destinationConfig.serviceName ?? destinationConfig.serviceId; + secondaryText = "Service"; + break; default: throw new Error(`Unhandled Destination Col Values ${destination}`); } diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/RenderSyncDestinationSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/RenderSyncDestinationSection.tsx new file mode 100644 index 000000000..b661caf00 --- /dev/null +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/RenderSyncDestinationSection.tsx @@ -0,0 +1,23 @@ +import { GenericFieldLabel } from "@app/components/secret-syncs"; +import { useRenderConnectionListServices } from "@app/hooks/api/appConnections/render"; +import { TRenderSync } from "@app/hooks/api/secretSyncs/render-sync"; + +type Props = { + secretSync: TRenderSync; +}; + +export const RenderSyncDestinationSection = ({ secretSync }: Props) => { + const { data: services = [], isPending } = useRenderConnectionListServices( + secretSync.connectionId + ); + const { + destinationConfig: { serviceId } + } = secretSync; + + if (isPending) { + return Loading...; + } + + const serviceName = services.find((service) => service.id === serviceId)?.name; + return {serviceName ?? serviceId}; +}; diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/SecretSyncDestinatonSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/SecretSyncDestinatonSection.tsx index 24cc3908a..bb8493f14 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/SecretSyncDestinatonSection.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/SecretSyncDestinatonSection.tsx @@ -23,6 +23,7 @@ import { GitHubSyncDestinationSection } from "./GitHubSyncDestinationSection"; import { HCVaultSyncDestinationSection } from "./HCVaultSyncDestinationSection"; import { HumanitecSyncDestinationSection } from "./HumanitecSyncDestinationSection"; import { OCIVaultSyncDestinationSection } from "./OCIVaultSyncDestinationSection"; +import { RenderSyncDestinationSection } from "./RenderSyncDestinationSection"; import { TeamCitySyncDestinationSection } from "./TeamCitySyncDestinationSection"; import { TerraformCloudSyncDestinationSection } from "./TerraformCloudSyncDestinationSection"; import { VercelSyncDestinationSection } from "./VercelSyncDestinationSection"; @@ -93,6 +94,9 @@ export const SecretSyncDestinationSection = ({ secretSync, onEditDestination }: case SecretSync.AzureDevOps: DestinationComponents = ; break; + case SecretSync.Render: + DestinationComponents = ; + break; default: throw new Error(`Unhandled Destination Section components: ${destination}`); } 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 e02d2ff84..735b5303f 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx @@ -52,6 +52,7 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) = case SecretSync.TeamCity: case SecretSync.OCIVault: case SecretSync.OnePass: + case SecretSync.Render: AdditionalSyncOptionsComponent = null; break; default: