From 36954a9df96ffcb9edb616492bde5f4df113a396 Mon Sep 17 00:00:00 2001 From: x032205 Date: Wed, 7 May 2025 17:57:00 -0400 Subject: [PATCH] secret sync + tweaks --- backend/src/lib/api-docs/constants.ts | 2 +- .../oci-connection-router.ts | 28 +++ .../routes/v1/secret-sync-routers/index.ts | 4 +- .../oci-vault-sync-router.ts | 17 ++ .../secret-sync-routers/secret-sync-router.ts | 7 +- .../app-connection/oci/oci-connection-fns.ts | 27 ++- .../oci/oci-connection-service.ts | 14 +- .../services/secret-sync/oci-vault/index.ts | 4 + .../oci-vault/oci-vault-sync-constants.ts | 10 + .../oci-vault/oci-vault-sync-fns.ts | 218 ++++++++++++++++++ .../oci-vault/oci-vault-sync-schemas.ts | 53 +++++ .../oci-vault/oci-vault-sync-types.ts | 44 ++++ .../services/secret-sync/secret-sync-enums.ts | 3 +- .../services/secret-sync/secret-sync-fns.ts | 11 +- .../services/secret-sync/secret-sync-maps.ts | 6 +- .../services/secret-sync/secret-sync-types.ts | 13 +- .../secret-syncs/SecretSyncStatusBadge.tsx | 9 +- .../OCIVaultSyncFields.tsx | 179 ++++++++++++++ .../SecretSyncDestinationFields.tsx | 3 + .../SecretSyncOptionsFields.tsx | 1 + .../OCIVaultSyncReviewFields.tsx | 20 ++ .../SecretSyncReviewFields.tsx | 4 + .../oci-vault-sync-destination-schema.ts | 15 ++ .../forms/schemas/secret-sync-schema.ts | 4 +- frontend/src/helpers/secretSyncs.ts | 7 +- .../hooks/api/appConnections/oci/queries.tsx | 45 +++- .../src/hooks/api/appConnections/oci/types.ts | 8 +- frontend/src/hooks/api/secretSyncs/enums.ts | 3 +- .../src/hooks/api/secretSyncs/types/index.ts | 4 +- .../api/secretSyncs/types/oci-vault-sync.ts | 17 ++ .../OCIVaultSyncDestinationCol.tsx | 14 ++ .../SecretSyncDestinationCol.tsx | 3 + .../SecretSyncTable/helpers/index.ts | 5 + .../OCIVaultSyncDestinationSection.tsx | 20 ++ .../SecretSyncDestinatonSection.tsx | 4 + .../SecretSyncOptionsSection.tsx | 1 + 36 files changed, 799 insertions(+), 28 deletions(-) create mode 100644 backend/src/server/routes/v1/secret-sync-routers/oci-vault-sync-router.ts create mode 100644 backend/src/services/secret-sync/oci-vault/index.ts create mode 100644 backend/src/services/secret-sync/oci-vault/oci-vault-sync-constants.ts create mode 100644 backend/src/services/secret-sync/oci-vault/oci-vault-sync-fns.ts create mode 100644 backend/src/services/secret-sync/oci-vault/oci-vault-sync-schemas.ts create mode 100644 backend/src/services/secret-sync/oci-vault/oci-vault-sync-types.ts create mode 100644 frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/OCIVaultSyncFields.tsx create mode 100644 frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/OCIVaultSyncReviewFields.tsx create mode 100644 frontend/src/components/secret-syncs/forms/schemas/oci-vault-sync-destination-schema.ts create mode 100644 frontend/src/hooks/api/secretSyncs/types/oci-vault-sync.ts create mode 100644 frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/OCIVaultSyncDestinationCol.tsx create mode 100644 frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/OCIVaultSyncDestinationSection.tsx diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 232923e60..d70c70399 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -2084,7 +2084,7 @@ export const SecretSyncs = { OCI_VAULT: { compartmentOcid: "The OCID (Oracle Cloud Identifier) of the compartment where the vault is located.", vaultOcid: "The OCID (Oracle Cloud Identifier) of the vault to sync secrets to.", - keyOcid: "The OCID (Oracle Cloud Identifier) of the encryption key to use when creating secrets in the vault." // TODO(andrey): May or may not be needed. This refers to encryption key. + keyOcid: "The OCID (Oracle Cloud Identifier) of the encryption key to use when creating secrets in the vault." } } }; diff --git a/backend/src/server/routes/v1/app-connection-routers/oci-connection-router.ts b/backend/src/server/routes/v1/app-connection-routers/oci-connection-router.ts index c429d6212..3398ec8a8 100644 --- a/backend/src/server/routes/v1/app-connection-routers/oci-connection-router.ts +++ b/backend/src/server/routes/v1/app-connection-routers/oci-connection-router.ts @@ -22,6 +22,34 @@ export const registerOCIConnectionRouter = async (server: FastifyZodProvider) => }); // The following endpoints are for internal Infisical App use only and not part of the public API + server.route({ + method: "GET", + url: `/:connectionId/compartments`, + 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 compartments = await server.services.appConnection.oci.listCompartments(connectionId, req.permission); + return compartments; + } + }); + server.route({ method: "GET", url: `/:connectionId/vaults`, 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 75b3ac68e..b5bd62ad6 100644 --- a/backend/src/server/routes/v1/secret-sync-routers/index.ts +++ b/backend/src/server/routes/v1/secret-sync-routers/index.ts @@ -10,6 +10,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 { registerOCIVaultSyncRouter } from "./oci-vault-sync-router"; import { registerTeamCitySyncRouter } from "./teamcity-sync-router"; import { registerTerraformCloudSyncRouter } from "./terraform-cloud-sync-router"; import { registerVercelSyncRouter } from "./vercel-sync-router"; @@ -31,5 +32,6 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record + registerSyncSecretsEndpoints({ + destination: SecretSync.OCIVault, + server, + responseSchema: OCIVaultSyncSchema, + createSchema: CreateOCIVaultSyncSchema, + updateSchema: UpdateOCIVaultSyncSchema + }); 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 359040d7f..a7a561738 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 @@ -24,6 +24,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 { OCIVaultSyncListItemSchema, OCIVaultSyncSchema } from "@app/services/secret-sync/oci-vault"; 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"; @@ -43,7 +44,8 @@ const SecretSyncSchema = z.discriminatedUnion("destination", [ VercelSyncSchema, WindmillSyncSchema, HCVaultSyncSchema, - TeamCitySyncSchema + TeamCitySyncSchema, + OCIVaultSyncSchema ]); const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [ @@ -60,7 +62,8 @@ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [ VercelSyncListItemSchema, WindmillSyncListItemSchema, HCVaultSyncListItemSchema, - TeamCitySyncListItemSchema + TeamCitySyncListItemSchema, + OCIVaultSyncListItemSchema ]); export const registerSecretSyncRouter = async (server: FastifyZodProvider) => { diff --git a/backend/src/services/app-connection/oci/oci-connection-fns.ts b/backend/src/services/app-connection/oci/oci-connection-fns.ts index 4582ac996..fb6e5ce5a 100644 --- a/backend/src/services/app-connection/oci/oci-connection-fns.ts +++ b/backend/src/services/app-connection/oci/oci-connection-fns.ts @@ -1,6 +1,5 @@ -import { common, identity, keymanagement, vault } from "oci-sdk"; +import { common, identity, keymanagement } from "oci-sdk"; -import { request } from "@app/lib/config/request"; import { BadRequestError } from "@app/lib/errors"; import { AppConnection } from "@app/services/app-connection/app-connection-enums"; @@ -56,6 +55,30 @@ export const validateOCIConnectionCredentials = async (config: TOCIConnectionCon return config.credentials; }; +export const listOCICompartments = async (appConnection: TOCIConnection) => { + const provider = await getOCIProvider(appConnection); + + const identityClient = new identity.IdentityClient({ authenticationDetailsProvider: provider }); + + const rootCompartment = await identityClient + .getTenancy({ + tenancyId: appConnection.credentials.tenancyOcid + }) + .then((response) => ({ + ...response.tenancy, + id: appConnection.credentials.tenancyOcid, + name: response.tenancy.name ? `${response.tenancy.name} (root)` : "root" + })); + + const compartments = await identityClient.listCompartments({ + compartmentId: appConnection.credentials.tenancyOcid, + compartmentIdInSubtree: true, + accessLevel: identity.requests.ListCompartmentsRequest.AccessLevel.Any + }); + + return [rootCompartment, ...compartments.items]; +}; + export const listOCIVaults = async (appConnection: TOCIConnection, compartmentOcid: string) => { const provider = await getOCIProvider(appConnection); diff --git a/backend/src/services/app-connection/oci/oci-connection-service.ts b/backend/src/services/app-connection/oci/oci-connection-service.ts index 600cc7853..d49d795ef 100644 --- a/backend/src/services/app-connection/oci/oci-connection-service.ts +++ b/backend/src/services/app-connection/oci/oci-connection-service.ts @@ -1,7 +1,7 @@ import { OrgServiceActor } from "@app/lib/types"; import { AppConnection } from "../app-connection-enums"; -import { listOCIVaultKeys, listOCIVaults } from "./oci-connection-fns"; +import { listOCICompartments, listOCIVaultKeys, listOCIVaults } from "./oci-connection-fns"; import { TOCIConnection } from "./oci-connection-types"; type TGetAppConnectionFunc = ( @@ -22,6 +22,17 @@ type TListOCIVaultKeysDTO = { }; export const ociConnectionService = (getAppConnection: TGetAppConnectionFunc) => { + const listCompartments = async (connectionId: string, actor: OrgServiceActor) => { + const appConnection = await getAppConnection(AppConnection.OCI, connectionId, actor); + + try { + const compartments = await listOCICompartments(appConnection); + return compartments; + } catch (error) { + return []; + } + }; + const listVaults = async ({ connectionId, compartmentOcid }: TListOCIVaultsDTO, actor: OrgServiceActor) => { const appConnection = await getAppConnection(AppConnection.OCI, connectionId, actor); @@ -48,6 +59,7 @@ export const ociConnectionService = (getAppConnection: TGetAppConnectionFunc) => }; return { + listCompartments, listVaults, listVaultKeys }; diff --git a/backend/src/services/secret-sync/oci-vault/index.ts b/backend/src/services/secret-sync/oci-vault/index.ts new file mode 100644 index 000000000..cee990de4 --- /dev/null +++ b/backend/src/services/secret-sync/oci-vault/index.ts @@ -0,0 +1,4 @@ +export * from "./oci-vault-sync-constants"; +export * from "./oci-vault-sync-fns"; +export * from "./oci-vault-sync-schemas"; +export * from "./oci-vault-sync-types"; diff --git a/backend/src/services/secret-sync/oci-vault/oci-vault-sync-constants.ts b/backend/src/services/secret-sync/oci-vault/oci-vault-sync-constants.ts new file mode 100644 index 000000000..9e2aad056 --- /dev/null +++ b/backend/src/services/secret-sync/oci-vault/oci-vault-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 OCI_VAULT_SYNC_LIST_OPTION: TSecretSyncListItem = { + name: "OCI Vault", + destination: SecretSync.OCIVault, + connection: AppConnection.OCI, + canImportSecrets: true +}; diff --git a/backend/src/services/secret-sync/oci-vault/oci-vault-sync-fns.ts b/backend/src/services/secret-sync/oci-vault/oci-vault-sync-fns.ts new file mode 100644 index 000000000..52e36232c --- /dev/null +++ b/backend/src/services/secret-sync/oci-vault/oci-vault-sync-fns.ts @@ -0,0 +1,218 @@ +import { secrets, vault } from "oci-sdk"; + +import { getOCIProvider } from "@app/services/app-connection/oci"; +import { + TCreateOCIVaultVariable, + TDeleteOCIVaultVariable, + TOCIVaultListVariables, + TOCIVaultSyncWithCredentials, + TUpdateOCIVaultVariable +} from "@app/services/secret-sync/oci-vault/oci-vault-sync-types"; +import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors"; +import { TSecretMap } from "@app/services/secret-sync/secret-sync-types"; + +const listOCIVaultVariables = async ({ provider, compartmentId, vaultId, onlyActive }: TOCIVaultListVariables) => { + const vaultsClient = new vault.VaultsClient({ authenticationDetailsProvider: provider }); + const secretsClient = new secrets.SecretsClient({ authenticationDetailsProvider: provider }); + + const secretsRes = await vaultsClient.listSecrets({ + compartmentId, + vaultId, + lifecycleState: onlyActive ? vault.models.SecretSummary.LifecycleState.Active : undefined + }); + + const result: Record = {}; + + for await (const s of secretsRes.items) { + let secretValue = ""; + + if (s.lifecycleState === vault.models.SecretSummary.LifecycleState.Active) { + const secretBundle = await secretsClient.getSecretBundle({ + secretId: s.id + }); + + secretValue = Buffer.from(secretBundle.secretBundle.secretBundleContent?.content || "", "base64").toString( + "utf-8" + ); + } + + result[s.secretName] = { + ...s, + name: s.secretName, + value: secretValue + }; + } + + return result; +}; + +const createOCIVaultVariable = async ({ + provider, + compartmentId, + vaultId, + keyId, + name, + value +}: TCreateOCIVaultVariable) => { + if (!value) return; + + const vaultsClient = new vault.VaultsClient({ authenticationDetailsProvider: provider }); + + return vaultsClient.createSecret({ + createSecretDetails: { + compartmentId, + vaultId, + keyId, + secretName: name, + enableAutoGeneration: false, + secretContent: { + content: Buffer.from(value).toString("base64"), + contentType: "BASE64" + } + } + }); +}; + +const updateOCIVaultVariable = async ({ provider, secretId, value }: TUpdateOCIVaultVariable) => { + if (!value) return; + + const vaultsClient = new vault.VaultsClient({ authenticationDetailsProvider: provider }); + + return vaultsClient.updateSecret({ + secretId, + updateSecretDetails: { + enableAutoGeneration: false, + secretContent: { + content: Buffer.from(value).toString("base64"), + contentType: "BASE64" + } + } + }); +}; + +const deleteOCIVaultVariable = async ({ provider, secretId }: TDeleteOCIVaultVariable) => { + const vaultsClient = new vault.VaultsClient({ authenticationDetailsProvider: provider }); + + // Schedule a secret deletion 7 days from now. OCI Vault requires a MINIMUM buffer period of 7 days + return vaultsClient.scheduleSecretDeletion({ + secretId, + scheduleSecretDeletionDetails: { + timeOfDeletion: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) + } + }); +}; + +export const OCIVaultSyncFns = { + syncSecrets: async (secretSync: TOCIVaultSyncWithCredentials, secretMap: TSecretMap) => { + const { + connection, + destinationConfig: { compartmentOcid, vaultOcid, keyOcid } + } = secretSync; + + const provider = await getOCIProvider(connection); + const variables = await listOCIVaultVariables({ provider, compartmentId: compartmentOcid, vaultId: vaultOcid }); + + // Create secrets + for await (const entry of Object.entries(secretMap)) { + const [key, { value }] = entry; + + if (!Object.values(variables).some((v) => v.secretName === key)) { + try { + await createOCIVaultVariable({ + compartmentId: compartmentOcid, + vaultId: vaultOcid, + provider, + keyId: keyOcid, + name: key, + value + }); + } catch (error) { + throw new SecretSyncError({ + error, + secretKey: key + }); + } + } + } + + // Update and delete secrets + for await (const [key, variable] of Object.entries(variables)) { + // Only update / delete active secrets + if (variable.lifecycleState === vault.models.SecretSummary.LifecycleState.Active) { + if (key in secretMap) { + if (variable.value !== secretMap[key].value) { + try { + await updateOCIVaultVariable({ + compartmentId: compartmentOcid, + vaultId: vaultOcid, + provider, + secretId: variable.id, + value: secretMap[key].value + }); + } catch (error) { + throw new SecretSyncError({ + error, + secretKey: key + }); + } + } + } else if (!secretSync.syncOptions.disableSecretDeletion) { + try { + await deleteOCIVaultVariable({ + compartmentId: compartmentOcid, + vaultId: vaultOcid, + provider, + secretId: variable.id + }); + } catch (error) { + throw new SecretSyncError({ + error, + secretKey: key + }); + } + } + } + } + }, + removeSecrets: async (secretSync: TOCIVaultSyncWithCredentials, secretMap: TSecretMap) => { + const { + connection, + destinationConfig: { compartmentOcid, vaultOcid } + } = secretSync; + + const provider = await getOCIProvider(connection); + const variables = await listOCIVaultVariables({ + provider, + compartmentId: compartmentOcid, + vaultId: vaultOcid, + onlyActive: true + }); + + for await (const [key, variable] of Object.entries(variables)) { + if (key in secretMap) { + try { + await deleteOCIVaultVariable({ + compartmentId: compartmentOcid, + vaultId: vaultOcid, + provider, + secretId: variable.id + }); + } catch (error) { + throw new SecretSyncError({ + error, + secretKey: key + }); + } + } + } + }, + getSecrets: async (secretSync: TOCIVaultSyncWithCredentials) => { + const { + connection, + destinationConfig: { compartmentOcid, vaultOcid } + } = secretSync; + + const provider = await getOCIProvider(connection); + return listOCIVaultVariables({ provider, compartmentId: compartmentOcid, vaultId: vaultOcid, onlyActive: true }); + } +}; diff --git a/backend/src/services/secret-sync/oci-vault/oci-vault-sync-schemas.ts b/backend/src/services/secret-sync/oci-vault/oci-vault-sync-schemas.ts new file mode 100644 index 000000000..f2d1bb733 --- /dev/null +++ b/backend/src/services/secret-sync/oci-vault/oci-vault-sync-schemas.ts @@ -0,0 +1,53 @@ +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"; + +const OCIVaultSyncDestinationConfigSchema = z.object({ + compartmentOcid: z + .string() + .trim() + .min(1, "Compartment OCID required") + .describe(SecretSyncs.DESTINATION_CONFIG.OCI_VAULT.compartmentOcid), + vaultOcid: z + .string() + .trim() + .min(1, "Vault OCID required") + .describe(SecretSyncs.DESTINATION_CONFIG.OCI_VAULT.vaultOcid), + keyOcid: z.string().trim().min(1, "Key OCID required").describe(SecretSyncs.DESTINATION_CONFIG.OCI_VAULT.keyOcid) +}); + +const OCIVaultSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true }; + +export const OCIVaultSyncSchema = BaseSecretSyncSchema(SecretSync.OCIVault, OCIVaultSyncOptionsConfig).extend({ + destination: z.literal(SecretSync.OCIVault), + destinationConfig: OCIVaultSyncDestinationConfigSchema +}); + +export const CreateOCIVaultSyncSchema = GenericCreateSecretSyncFieldsSchema( + SecretSync.OCIVault, + OCIVaultSyncOptionsConfig +).extend({ + destinationConfig: OCIVaultSyncDestinationConfigSchema +}); + +export const UpdateOCIVaultSyncSchema = GenericUpdateSecretSyncFieldsSchema( + SecretSync.OCIVault, + OCIVaultSyncOptionsConfig +).extend({ + destinationConfig: OCIVaultSyncDestinationConfigSchema.optional() +}); + +export const OCIVaultSyncListItemSchema = z.object({ + name: z.literal("OCI Vault"), + connection: z.literal(AppConnection.OCI), + destination: z.literal(SecretSync.OCIVault), + canImportSecrets: z.literal(true) +}); diff --git a/backend/src/services/secret-sync/oci-vault/oci-vault-sync-types.ts b/backend/src/services/secret-sync/oci-vault/oci-vault-sync-types.ts new file mode 100644 index 000000000..08c4a498f --- /dev/null +++ b/backend/src/services/secret-sync/oci-vault/oci-vault-sync-types.ts @@ -0,0 +1,44 @@ +import { SimpleAuthenticationDetailsProvider } from "oci-sdk"; +import { z } from "zod"; + +import { TOCIConnection } from "@app/services/app-connection/oci"; + +import { CreateOCIVaultSyncSchema, OCIVaultSyncListItemSchema, OCIVaultSyncSchema } from "./oci-vault-sync-schemas"; + +export type TOCIVaultSync = z.infer; + +export type TOCIVaultSyncInput = z.infer; + +export type TOCIVaultSyncListItem = z.infer; + +export type TOCIVaultSyncWithCredentials = TOCIVaultSync & { + connection: TOCIConnection; +}; + +export type TOCIVaultVariable = { + id: string; + name: string; + value: string; +}; + +export type TOCIVaultListVariables = { + provider: SimpleAuthenticationDetailsProvider; + compartmentId: string; + vaultId: string; + onlyActive?: boolean; // Whether to filter for only active secrets. Removes deleted / scheduled for deletion secrets +}; + +export type TCreateOCIVaultVariable = TOCIVaultListVariables & { + keyId: string; + name: string; + value: string; +}; + +export type TUpdateOCIVaultVariable = TOCIVaultListVariables & { + secretId: string; + value: string; +}; + +export type TDeleteOCIVaultVariable = TOCIVaultListVariables & { + secretId: string; +}; diff --git a/backend/src/services/secret-sync/secret-sync-enums.ts b/backend/src/services/secret-sync/secret-sync-enums.ts index 9d59ebb76..a0982c5b6 100644 --- a/backend/src/services/secret-sync/secret-sync-enums.ts +++ b/backend/src/services/secret-sync/secret-sync-enums.ts @@ -12,7 +12,8 @@ export enum SecretSync { Vercel = "vercel", Windmill = "windmill", HCVault = "hashicorp-vault", - TeamCity = "teamcity" + TeamCity = "teamcity", + OCIVault = "oci-vault" } 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 5749852d7..d029a000a 100644 --- a/backend/src/services/secret-sync/secret-sync-fns.ts +++ b/backend/src/services/secret-sync/secret-sync-fns.ts @@ -28,6 +28,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 { OCI_VAULT_SYNC_LIST_OPTION, OCIVaultSyncFns } from "./oci-vault"; import { TEAMCITY_SYNC_LIST_OPTION, TeamCitySyncFns } from "./teamcity"; import { TERRAFORM_CLOUD_SYNC_LIST_OPTION, TerraformCloudSyncFns } from "./terraform-cloud"; import { VERCEL_SYNC_LIST_OPTION, VercelSyncFns } from "./vercel"; @@ -47,7 +48,8 @@ const SECRET_SYNC_LIST_OPTIONS: Record = { [SecretSync.Vercel]: VERCEL_SYNC_LIST_OPTION, [SecretSync.Windmill]: WINDMILL_SYNC_LIST_OPTION, [SecretSync.HCVault]: HC_VAULT_SYNC_LIST_OPTION, - [SecretSync.TeamCity]: TEAMCITY_SYNC_LIST_OPTION + [SecretSync.TeamCity]: TEAMCITY_SYNC_LIST_OPTION, + [SecretSync.OCIVault]: OCI_VAULT_SYNC_LIST_OPTION }; export const listSecretSyncOptions = () => { @@ -148,6 +150,8 @@ export const SecretSyncFns = { return HCVaultSyncFns.syncSecrets(secretSync, secretMap); case SecretSync.TeamCity: return TeamCitySyncFns.syncSecrets(secretSync, secretMap); + case SecretSync.OCIVault: + return OCIVaultSyncFns.syncSecrets(secretSync, secretMap); default: throw new Error( `Unhandled sync destination for sync secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}` @@ -213,6 +217,9 @@ export const SecretSyncFns = { case SecretSync.TeamCity: secretMap = await TeamCitySyncFns.getSecrets(secretSync); break; + case SecretSync.OCIVault: + secretMap = await OCIVaultSyncFns.getSecrets(secretSync); + break; default: throw new Error( `Unhandled sync destination for get secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}` @@ -270,6 +277,8 @@ export const SecretSyncFns = { return HCVaultSyncFns.removeSecrets(secretSync, secretMap); case SecretSync.TeamCity: return TeamCitySyncFns.removeSecrets(secretSync, secretMap); + case SecretSync.OCIVault: + return OCIVaultSyncFns.removeSecrets(secretSync, secretMap); 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 c6d7adc8c..21cb912b4 100644 --- a/backend/src/services/secret-sync/secret-sync-maps.ts +++ b/backend/src/services/secret-sync/secret-sync-maps.ts @@ -15,7 +15,8 @@ export const SECRET_SYNC_NAME_MAP: Record = { [SecretSync.Vercel]: "Vercel", [SecretSync.Windmill]: "Windmill", [SecretSync.HCVault]: "Hashicorp Vault", - [SecretSync.TeamCity]: "TeamCity" + [SecretSync.TeamCity]: "TeamCity", + [SecretSync.OCIVault]: "OCI Vault" }; export const SECRET_SYNC_CONNECTION_MAP: Record = { @@ -32,5 +33,6 @@ export const SECRET_SYNC_CONNECTION_MAP: Record = { [SecretSync.Vercel]: AppConnection.Vercel, [SecretSync.Windmill]: AppConnection.Windmill, [SecretSync.HCVault]: AppConnection.HCVault, - [SecretSync.TeamCity]: AppConnection.TeamCity + [SecretSync.TeamCity]: AppConnection.TeamCity, + [SecretSync.OCIVault]: AppConnection.OCI }; diff --git a/backend/src/services/secret-sync/secret-sync-types.ts b/backend/src/services/secret-sync/secret-sync-types.ts index e88174cc6..64d027e18 100644 --- a/backend/src/services/secret-sync/secret-sync-types.ts +++ b/backend/src/services/secret-sync/secret-sync-types.ts @@ -67,6 +67,7 @@ import { THumanitecSyncListItem, THumanitecSyncWithCredentials } from "./humanitec"; +import { TOCIVaultSync, TOCIVaultSyncInput, TOCIVaultSyncListItem, TOCIVaultSyncWithCredentials } from "./oci-vault"; import { TTeamCitySync, TTeamCitySyncInput, @@ -95,7 +96,8 @@ export type TSecretSync = | TVercelSync | TWindmillSync | THCVaultSync - | TTeamCitySync; + | TTeamCitySync + | TOCIVaultSync; export type TSecretSyncWithCredentials = | TAwsParameterStoreSyncWithCredentials @@ -111,7 +113,8 @@ export type TSecretSyncWithCredentials = | TVercelSyncWithCredentials | TWindmillSyncWithCredentials | THCVaultSyncWithCredentials - | TTeamCitySyncWithCredentials; + | TTeamCitySyncWithCredentials + | TOCIVaultSyncWithCredentials; export type TSecretSyncInput = | TAwsParameterStoreSyncInput @@ -127,7 +130,8 @@ export type TSecretSyncInput = | TVercelSyncInput | TWindmillSyncInput | THCVaultSyncInput - | TTeamCitySyncInput; + | TTeamCitySyncInput + | TOCIVaultSyncInput; export type TSecretSyncListItem = | TAwsParameterStoreSyncListItem @@ -143,7 +147,8 @@ export type TSecretSyncListItem = | TVercelSyncListItem | TWindmillSyncListItem | THCVaultSyncListItem - | TTeamCitySyncListItem; + | TTeamCitySyncListItem + | TOCIVaultSyncListItem; export type TSyncOptionsConfig = { canImportSecrets: boolean; diff --git a/frontend/src/components/secret-syncs/SecretSyncStatusBadge.tsx b/frontend/src/components/secret-syncs/SecretSyncStatusBadge.tsx index dbf543f61..53b53d5c0 100644 --- a/frontend/src/components/secret-syncs/SecretSyncStatusBadge.tsx +++ b/frontend/src/components/secret-syncs/SecretSyncStatusBadge.tsx @@ -40,7 +40,14 @@ export const SecretSyncStatusBadge = ({ status }: Props) => { return ( - + {text} ); diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/OCIVaultSyncFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/OCIVaultSyncFields.tsx new file mode 100644 index 000000000..0fd6b8473 --- /dev/null +++ b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/OCIVaultSyncFields.tsx @@ -0,0 +1,179 @@ +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 { + useOCIConnectionListCompartments, + useOCIConnectionListVaultKeys, + useOCIConnectionListVaults +} from "@app/hooks/api/appConnections/oci"; +import { SecretSync } from "@app/hooks/api/secretSyncs"; + +import { TSecretSyncForm } from "../schemas"; + +export const OCIVaultSyncFields = () => { + const { control, setValue } = useFormContext< + TSecretSyncForm & { destination: SecretSync.OCIVault } + >(); + + const connectionId = useWatch({ name: "connection.id", control }); + + // Compartments + const { data: compartments, isLoading: isCompartmentsLoading } = useOCIConnectionListCompartments( + connectionId, + { + enabled: Boolean(connectionId) + } + ); + + // Vaults + const selectedCompartment = useWatch({ name: "destinationConfig.compartmentOcid", control }); + const { data: vaults, isLoading: isVaultsLoading } = useOCIConnectionListVaults( + { connectionId, compartmentOcid: selectedCompartment }, + { + enabled: Boolean(connectionId) + } + ); + + // Keys + const selectedVault = useWatch({ name: "destinationConfig.vaultOcid", control }); + const { data: keys, isLoading: isKeysLoading } = useOCIConnectionListVaultKeys( + { connectionId, compartmentOcid: selectedCompartment, vaultOcid: selectedVault }, + { + enabled: Boolean(connectionId) + } + ); + + return ( + <> + { + setValue("destinationConfig.compartmentOcid", ""); + setValue("destinationConfig.vaultOcid", ""); + setValue("destinationConfig.keyOcid", ""); + }} + /> + + ( + +
+ Don't see the compartment you're looking for?{" "} + +
+ + } + > + c.id === value) ?? null} + onChange={(option) => { + onChange((option as SingleValue<{ id: string }>)?.id ?? null); + setValue("destinationConfig.vaultOcid", ""); + setValue("destinationConfig.keyOcid", ""); + }} + options={compartments} + placeholder="Select a compartment..." + getOptionLabel={(option) => option.name} + getOptionValue={(option) => option.id} + /> +
+ )} + /> + + ( + +
+ Don't see the vault you're looking for?{" "} + +
+ + } + > + v.id === value) ?? null} + onChange={(option) => { + onChange((option as SingleValue<{ id: string }>)?.id ?? null); + setValue("destinationConfig.keyOcid", ""); + }} + options={vaults} + isClearable + placeholder="Select a vault..." + getOptionLabel={(option) => option.displayName} + getOptionValue={(option) => option.id} + /> +
+ )} + /> + + ( + +
+ Don't see the key you're looking for?{" "} + +
+ + } + > + v.id === value) ?? null} + onChange={(option) => { + onChange((option as SingleValue<{ id: string }>)?.id ?? null); + }} + options={keys} + isClearable + placeholder="Select a key..." + getOptionLabel={(option) => option.displayName} + getOptionValue={(option) => option.id} + /> +
+ )} + /> + + ); +}; diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/SecretSyncDestinationFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/SecretSyncDestinationFields.tsx index 1d7a1dd55..2cac1ae20 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/SecretSyncDestinationFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/SecretSyncDestinationFields.tsx @@ -13,6 +13,7 @@ import { GcpSyncFields } from "./GcpSyncFields"; import { GitHubSyncFields } from "./GitHubSyncFields"; import { HCVaultSyncFields } from "./HCVaultSyncFields"; import { HumanitecSyncFields } from "./HumanitecSyncFields"; +import { OCIVaultSyncFields } from "./OCIVaultSyncFields"; import { TeamCitySyncFields } from "./TeamCitySyncFields"; import { TerraformCloudSyncFields } from "./TerraformCloudSyncFields"; import { VercelSyncFields } from "./VercelSyncFields"; @@ -52,6 +53,8 @@ export const SecretSyncDestinationFields = () => { return ; case SecretSync.TeamCity: return ; + case SecretSync.OCIVault: + 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 e4aa4ad65..9eec461cd 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx @@ -45,6 +45,7 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => { case SecretSync.Windmill: case SecretSync.HCVault: case SecretSync.TeamCity: + case SecretSync.OCIVault: AdditionalSyncOptionsFieldsComponent = null; break; default: diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/OCIVaultSyncReviewFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/OCIVaultSyncReviewFields.tsx new file mode 100644 index 000000000..5c44988bb --- /dev/null +++ b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/OCIVaultSyncReviewFields.tsx @@ -0,0 +1,20 @@ +import { useFormContext } from "react-hook-form"; + +import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas"; +import { GenericFieldLabel } from "@app/components/v2"; +import { SecretSync } from "@app/hooks/api/secretSyncs"; + +export const OCIVaultSyncReviewFields = () => { + const { watch } = useFormContext(); + const compartmentOcid = watch("destinationConfig.compartmentOcid"); + const vaultOcid = watch("destinationConfig.vaultOcid"); + const keyOcid = watch("destinationConfig.keyOcid"); + + return ( + <> + {compartmentOcid} + {vaultOcid} + {keyOcid} + + ); +}; diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx index 62402e540..b7f62c139 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx @@ -23,6 +23,7 @@ import { GcpSyncReviewFields } from "./GcpSyncReviewFields"; import { GitHubSyncReviewFields } from "./GitHubSyncReviewFields"; import { HCVaultSyncReviewFields } from "./HCVaultSyncReviewFields"; import { HumanitecSyncReviewFields } from "./HumanitecSyncReviewFields"; +import { OCIVaultSyncReviewFields } from "./OCIVaultSyncReviewFields"; import { TeamCitySyncReviewFields } from "./TeamCitySyncReviewFields"; import { TerraformCloudSyncReviewFields } from "./TerraformCloudSyncReviewFields"; import { VercelSyncReviewFields } from "./VercelSyncReviewFields"; @@ -96,6 +97,9 @@ export const SecretSyncReviewFields = () => { case SecretSync.TeamCity: DestinationFieldsComponent = ; break; + case SecretSync.OCIVault: + DestinationFieldsComponent = ; + break; default: throw new Error(`Unhandled Destination Review Fields: ${destination}`); } diff --git a/frontend/src/components/secret-syncs/forms/schemas/oci-vault-sync-destination-schema.ts b/frontend/src/components/secret-syncs/forms/schemas/oci-vault-sync-destination-schema.ts new file mode 100644 index 000000000..54d48a59b --- /dev/null +++ b/frontend/src/components/secret-syncs/forms/schemas/oci-vault-sync-destination-schema.ts @@ -0,0 +1,15 @@ +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 OCIVaultSyncDestinationSchema = BaseSecretSyncSchema().merge( + z.object({ + destination: z.literal(SecretSync.OCIVault), + destinationConfig: z.object({ + compartmentOcid: z.string().trim().min(1, "Compartment OCID required"), + vaultOcid: z.string().trim().min(1, "Vault OCID required"), + keyOcid: z.string().trim().min(1, "Key OCID required") + }) + }) +); 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 bc6184bc7..232b8cedf 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 @@ -10,6 +10,7 @@ import { GcpSyncDestinationSchema } from "./gcp-sync-destination-schema"; 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 { TeamCitySyncDestinationSchema } from "./teamcity-sync-destination-schema"; import { TerraformCloudSyncDestinationSchema } from "./terraform-cloud-destination-schema"; import { VercelSyncDestinationSchema } from "./vercel-sync-destination-schema"; @@ -29,7 +30,8 @@ const SecretSyncUnionSchema = z.discriminatedUnion("destination", [ VercelSyncDestinationSchema, WindmillSyncDestinationSchema, HCVaultSyncDestinationSchema, - TeamCitySyncDestinationSchema + TeamCitySyncDestinationSchema, + OCIVaultSyncDestinationSchema ]); export const SecretSyncFormSchema = SecretSyncUnionSchema; diff --git a/frontend/src/helpers/secretSyncs.ts b/frontend/src/helpers/secretSyncs.ts index 58d9f3e48..80df92ac3 100644 --- a/frontend/src/helpers/secretSyncs.ts +++ b/frontend/src/helpers/secretSyncs.ts @@ -47,6 +47,10 @@ export const SECRET_SYNC_MAP: Record = { [SecretSync.Vercel]: AppConnection.Vercel, [SecretSync.Windmill]: AppConnection.Windmill, [SecretSync.HCVault]: AppConnection.HCVault, - [SecretSync.TeamCity]: AppConnection.TeamCity + [SecretSync.TeamCity]: AppConnection.TeamCity, + [SecretSync.OCIVault]: AppConnection.OCI }; export const SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP: Record< diff --git a/frontend/src/hooks/api/appConnections/oci/queries.tsx b/frontend/src/hooks/api/appConnections/oci/queries.tsx index 13f7c4f7c..f0e2659b7 100644 --- a/frontend/src/hooks/api/appConnections/oci/queries.tsx +++ b/frontend/src/hooks/api/appConnections/oci/queries.tsx @@ -3,12 +3,47 @@ import { useQuery, UseQueryOptions } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; import { appConnectionKeys } from "../queries"; -import { TListOCIVaultKeys, TListOCIVaults, TOCIVault, TOCIVaultKey } from "./types"; +import { + TListOCIVaultKeys, + TListOCIVaults, + TOCICompartment, + TOCIVault, + TOCIVaultKey +} from "./types"; const ociConnectionKeys = { all: [...appConnectionKeys.all, "oci"] as const, - listVaults: (connectionId: string) => [...ociConnectionKeys.all, "vaults", connectionId] as const, - listVaultKeys: (connectionId: string) => [...ociConnectionKeys.all, "keys", connectionId] as const + listCompartments: (connectionId: string) => + [...ociConnectionKeys.all, "compartments", connectionId] as const, + listVaults: (connectionId: string, compartmentOcid: string) => + [...ociConnectionKeys.all, "vaults", connectionId, compartmentOcid] as const, + listVaultKeys: (connectionId: string, compartmentOcid: string, vaultOcid: string) => + [...ociConnectionKeys.all, "keys", connectionId, compartmentOcid, vaultOcid] as const +}; + +export const useOCIConnectionListCompartments = ( + connectionId: string, + options?: Omit< + UseQueryOptions< + TOCICompartment[], + unknown, + TOCICompartment[], + ReturnType + >, + "queryKey" | "queryFn" + > +) => { + return useQuery({ + queryKey: ociConnectionKeys.listCompartments(connectionId), + queryFn: async () => { + const { data } = await apiRequest.get( + `/api/v1/app-connections/oci/${connectionId}/compartments` + ); + + return data; + }, + ...options + }); }; export const useOCIConnectionListVaults = ( @@ -24,7 +59,7 @@ export const useOCIConnectionListVaults = ( > ) => { return useQuery({ - queryKey: ociConnectionKeys.listVaults(connectionId), + queryKey: ociConnectionKeys.listVaults(connectionId, compartmentOcid), queryFn: async () => { const { data } = await apiRequest.get( `/api/v1/app-connections/oci/${connectionId}/vaults`, @@ -54,7 +89,7 @@ export const useOCIConnectionListVaultKeys = ( > ) => { return useQuery({ - queryKey: ociConnectionKeys.listVaultKeys(connectionId), + queryKey: ociConnectionKeys.listVaultKeys(connectionId, compartmentOcid, vaultOcid), queryFn: async () => { const { data } = await apiRequest.get( `/api/v1/app-connections/oci/${connectionId}/vault-keys`, diff --git a/frontend/src/hooks/api/appConnections/oci/types.ts b/frontend/src/hooks/api/appConnections/oci/types.ts index 82fa6d57d..da12116bd 100644 --- a/frontend/src/hooks/api/appConnections/oci/types.ts +++ b/frontend/src/hooks/api/appConnections/oci/types.ts @@ -1,10 +1,12 @@ // Response types +export type TOCICompartment = { + id: string; + name: string; +}; + export type TOCIVault = { id: string; displayName: string; - compartmentId: string; - timeCreated: string; - lifecycleState: string; }; export type TOCIVaultKey = { diff --git a/frontend/src/hooks/api/secretSyncs/enums.ts b/frontend/src/hooks/api/secretSyncs/enums.ts index d078765bc..65a31e427 100644 --- a/frontend/src/hooks/api/secretSyncs/enums.ts +++ b/frontend/src/hooks/api/secretSyncs/enums.ts @@ -12,7 +12,8 @@ export enum SecretSync { Vercel = "vercel", Windmill = "windmill", HCVault = "hashicorp-vault", - TeamCity = "teamcity" + TeamCity = "teamcity", + OCIVault = "oci-vault" } export enum SecretSyncStatus { diff --git a/frontend/src/hooks/api/secretSyncs/types/index.ts b/frontend/src/hooks/api/secretSyncs/types/index.ts index 2dba65649..e3de6029a 100644 --- a/frontend/src/hooks/api/secretSyncs/types/index.ts +++ b/frontend/src/hooks/api/secretSyncs/types/index.ts @@ -11,6 +11,7 @@ import { TGcpSync } from "./gcp-sync"; import { TGitHubSync } from "./github-sync"; import { THCVaultSync } from "./hc-vault-sync"; import { THumanitecSync } from "./humanitec-sync"; +import { TOCIVaultSync } from "./oci-vault-sync"; import { TTeamCitySync } from "./teamcity-sync"; import { TTerraformCloudSync } from "./terraform-cloud-sync"; import { TVercelSync } from "./vercel-sync"; @@ -36,7 +37,8 @@ export type TSecretSync = | TVercelSync | TWindmillSync | THCVaultSync - | TTeamCitySync; + | TTeamCitySync + | TOCIVaultSync; export type TListSecretSyncs = { secretSyncs: TSecretSync[] }; diff --git a/frontend/src/hooks/api/secretSyncs/types/oci-vault-sync.ts b/frontend/src/hooks/api/secretSyncs/types/oci-vault-sync.ts new file mode 100644 index 000000000..9dd0062f4 --- /dev/null +++ b/frontend/src/hooks/api/secretSyncs/types/oci-vault-sync.ts @@ -0,0 +1,17 @@ +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 TOCIVaultSync = TRootSecretSync & { + destination: SecretSync.OCIVault; + destinationConfig: { + compartmentOcid: string; + vaultOcid: string; + keyOcid: string; + }; + connection: { + app: AppConnection.OCI; + name: string; + id: string; + }; +}; diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/OCIVaultSyncDestinationCol.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/OCIVaultSyncDestinationCol.tsx new file mode 100644 index 000000000..e6d598269 --- /dev/null +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/OCIVaultSyncDestinationCol.tsx @@ -0,0 +1,14 @@ +import { TOCIVaultSync } from "@app/hooks/api/secretSyncs/types/oci-vault-sync"; + +import { getSecretSyncDestinationColValues } from "../helpers"; +import { SecretSyncTableCell } from "../SecretSyncTableCell"; + +type Props = { + secretSync: TOCIVaultSync; +}; + +export const OCIVaultSyncDestinationCol = ({ secretSync }: Props) => { + const { primaryText, secondaryText } = getSecretSyncDestinationColValues(secretSync); + + 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 abfbf100c..898980881 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 @@ -10,6 +10,7 @@ import { GcpSyncDestinationCol } from "./GcpSyncDestinationCol"; import { GitHubSyncDestinationCol } from "./GitHubSyncDestinationCol"; import { HCVaultSyncDestinationCol } from "./HCVaultSyncDestinationCol"; import { HumanitecSyncDestinationCol } from "./HumanitecSyncDestinationCol"; +import { OCIVaultSyncDestinationCol } from "./OCIVaultSyncDestinationCol"; import { TeamCitySyncDestinationCol } from "./TeamCitySyncDestinationCol"; import { TerraformCloudSyncDestinationCol } from "./TerraformCloudSyncDestinationCol"; import { VercelSyncDestinationCol } from "./VercelSyncDestinationCol"; @@ -49,6 +50,8 @@ export const SecretSyncDestinationCol = ({ secretSync }: Props) => { return ; case SecretSync.TeamCity: return ; + case SecretSync.OCIVault: + 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 b1fe387e5..f3a67d7ad 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 @@ -102,6 +102,11 @@ export const getSecretSyncDestinationColValues = (secretSync: TSecretSync) => { primaryText = destinationConfig.project; secondaryText = destinationConfig.buildConfig; break; + case SecretSync.OCIVault: + // TODO(andrey): Truncating these may make them look better + primaryText = destinationConfig.compartmentOcid; + secondaryText = destinationConfig.vaultOcid; + break; default: throw new Error(`Unhandled Destination Col Values ${destination}`); } diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/OCIVaultSyncDestinationSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/OCIVaultSyncDestinationSection.tsx new file mode 100644 index 000000000..19445a652 --- /dev/null +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/OCIVaultSyncDestinationSection.tsx @@ -0,0 +1,20 @@ +import { GenericFieldLabel } from "@app/components/secret-syncs"; +import { TOCIVaultSync } from "@app/hooks/api/secretSyncs/types/oci-vault-sync"; + +type Props = { + secretSync: TOCIVaultSync; +}; + +export const OCIVaultSyncDestinationSection = ({ secretSync }: Props) => { + const { + destinationConfig: { compartmentOcid, keyOcid, vaultOcid } + } = secretSync; + + return ( + <> + {compartmentOcid} + {vaultOcid} + {keyOcid} + + ); +}; 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 4ea5798d9..b0c989ee2 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/SecretSyncDestinatonSection.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/SecretSyncDestinatonSection.tsx @@ -20,6 +20,7 @@ import { GcpSyncDestinationSection } from "./GcpSyncDestinationSection"; import { GitHubSyncDestinationSection } from "./GitHubSyncDestinationSection"; import { HCVaultSyncDestinationSection } from "./HCVaultSyncDestinationSection"; import { HumanitecSyncDestinationSection } from "./HumanitecSyncDestinationSection"; +import { OCIVaultSyncDestinationSection } from "./OCIVaultSyncDestinationSection"; import { TeamCitySyncDestinationSection } from "./TeamCitySyncDestinationSection"; import { TerraformCloudSyncDestinationSection } from "./TerraformCloudSyncDestinationSection"; import { VercelSyncDestinationSection } from "./VercelSyncDestinationSection"; @@ -81,6 +82,9 @@ export const SecretSyncDestinationSection = ({ secretSync, onEditDestination }: case SecretSync.TeamCity: DestinationComponents = ; break; + case SecretSync.OCIVault: + 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 5995e7cd1..ca229b55c 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx @@ -54,6 +54,7 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) = case SecretSync.Windmill: case SecretSync.HCVault: case SecretSync.TeamCity: + case SecretSync.OCIVault: AdditionalSyncOptionsComponent = null; break; default: