From fe75627ab7fed363274d30f6a215bccb35478b17 Mon Sep 17 00:00:00 2001 From: x032205 Date: Mon, 16 Jun 2025 15:49:42 -0400 Subject: [PATCH] Fly.io secret sync --- backend/src/lib/api-docs/constants.ts | 3 + .../flyio-connection-router.ts | 1 - .../secret-sync-routers/flyio-sync-router.ts | 13 ++ .../routes/v1/secret-sync-routers/index.ts | 4 +- .../secret-sync-routers/secret-sync-router.ts | 7 +- .../flyio/flyio-connection-types.ts | 1 - .../secret-sync/flyio/flyio-sync-constants.ts | 10 ++ .../secret-sync/flyio/flyio-sync-fns.ts | 132 ++++++++++++++++++ .../secret-sync/flyio/flyio-sync-schemas.ts | 43 ++++++ .../secret-sync/flyio/flyio-sync-types.ts | 32 +++++ .../src/services/secret-sync/flyio/index.ts | 4 + .../services/secret-sync/secret-sync-enums.ts | 3 +- .../services/secret-sync/secret-sync-fns.ts | 11 +- .../services/secret-sync/secret-sync-maps.ts | 9 +- .../services/secret-sync/secret-sync-types.ts | 13 +- .../FlyioSyncFields.tsx | 51 +++++++ .../SecretSyncDestinationFields.tsx | 3 + .../SecretSyncOptionsFields.tsx | 1 + .../FlyioSyncReviewFields.tsx | 12 ++ .../SecretSyncReviewFields.tsx | 4 + .../schemas/flyio-sync-destination-schema.ts | 13 ++ .../forms/schemas/secret-sync-schema.ts | 4 +- frontend/src/helpers/secretSyncs.ts | 7 +- .../hooks/api/appConnections/flyio/types.ts | 1 - frontend/src/hooks/api/secretSyncs/enums.ts | 3 +- .../hooks/api/secretSyncs/types/flyio-sync.ts | 15 ++ .../src/hooks/api/secretSyncs/types/index.ts | 4 +- .../FlyioSyncDestinationCol.tsx | 14 ++ .../SecretSyncDestinationCol.tsx | 3 + .../SecretSyncTable/helpers/index.ts | 4 + .../FlyioSyncDestinationSection.tsx | 14 ++ .../SecretSyncDestinatonSection.tsx | 4 + .../SecretSyncOptionsSection.tsx | 1 + 33 files changed, 425 insertions(+), 19 deletions(-) create mode 100644 backend/src/server/routes/v1/secret-sync-routers/flyio-sync-router.ts create mode 100644 backend/src/services/secret-sync/flyio/flyio-sync-constants.ts create mode 100644 backend/src/services/secret-sync/flyio/flyio-sync-fns.ts create mode 100644 backend/src/services/secret-sync/flyio/flyio-sync-schemas.ts create mode 100644 backend/src/services/secret-sync/flyio/flyio-sync-types.ts create mode 100644 backend/src/services/secret-sync/flyio/index.ts create mode 100644 frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/FlyioSyncFields.tsx create mode 100644 frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/FlyioSyncReviewFields.tsx create mode 100644 frontend/src/components/secret-syncs/forms/schemas/flyio-sync-destination-schema.ts create mode 100644 frontend/src/hooks/api/secretSyncs/types/flyio-sync.ts create mode 100644 frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/FlyioSyncDestinationCol.tsx create mode 100644 frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/FlyioSyncDestinationSection.tsx diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index cc841e392..79e8d0789 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -2387,6 +2387,9 @@ export const SecretSyncs = { }, ONEPASS: { vaultId: "The ID of the 1Password vault to sync secrets to." + }, + FLYIO: { + appId: "The ID of the Fly.io app to sync secrets to." } } }; diff --git a/backend/src/server/routes/v1/app-connection-routers/flyio-connection-router.ts b/backend/src/server/routes/v1/app-connection-routers/flyio-connection-router.ts index 3a43570d9..c2a1199aa 100644 --- a/backend/src/server/routes/v1/app-connection-routers/flyio-connection-router.ts +++ b/backend/src/server/routes/v1/app-connection-routers/flyio-connection-router.ts @@ -35,7 +35,6 @@ export const registerFlyioConnectionRouter = async (server: FastifyZodProvider) response: { 200: z .object({ - // TODO(andrey): apps id: z.string(), name: z.string() }) diff --git a/backend/src/server/routes/v1/secret-sync-routers/flyio-sync-router.ts b/backend/src/server/routes/v1/secret-sync-routers/flyio-sync-router.ts new file mode 100644 index 000000000..30501078e --- /dev/null +++ b/backend/src/server/routes/v1/secret-sync-routers/flyio-sync-router.ts @@ -0,0 +1,13 @@ +import { CreateFlyioSyncSchema, FlyioSyncSchema, UpdateFlyioSyncSchema } from "@app/services/secret-sync/flyio"; +import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; + +import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints"; + +export const registerFlyioSyncRouter = async (server: FastifyZodProvider) => + registerSyncSecretsEndpoints({ + destination: SecretSync.Flyio, + server, + responseSchema: FlyioSyncSchema, + createSchema: CreateFlyioSyncSchema, + updateSchema: UpdateFlyioSyncSchema + }); 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..39d6747a4 100644 --- a/backend/src/server/routes/v1/secret-sync-routers/index.ts +++ b/backend/src/server/routes/v1/secret-sync-routers/index.ts @@ -9,6 +9,7 @@ import { registerAzureDevOpsSyncRouter } from "./azure-devops-sync-router"; import { registerAzureKeyVaultSyncRouter } from "./azure-key-vault-sync-router"; import { registerCamundaSyncRouter } from "./camunda-sync-router"; import { registerDatabricksSyncRouter } from "./databricks-sync-router"; +import { registerFlyioSyncRouter } from "./flyio-sync-router"; import { registerGcpSyncRouter } from "./gcp-sync-router"; import { registerGitHubSyncRouter } from "./github-sync-router"; import { registerHCVaultSyncRouter } from "./hc-vault-sync-router"; @@ -37,5 +38,6 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record { diff --git a/backend/src/services/app-connection/flyio/flyio-connection-types.ts b/backend/src/services/app-connection/flyio/flyio-connection-types.ts index 95e948b64..f643caffb 100644 --- a/backend/src/services/app-connection/flyio/flyio-connection-types.ts +++ b/backend/src/services/app-connection/flyio/flyio-connection-types.ts @@ -21,7 +21,6 @@ export type TFlyioConnectionConfig = DiscriminativePick { + const { data } = await request.post<{ data: { app: { secrets: TFlyioSecret[] } } }>( + "https://api.fly.io/graphql", + { + query: "query GetAppSecrets($appId: String!) { app(id: $appId) { id name secrets { name createdAt } } }", + variables: { appId } + }, + { + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json", + Accept: "application/json" + } + } + ); + + return data.data.app.secrets.map((s) => s.name); +}; + +const putFlyioSecrets = async ({ accessToken, appId, secretMap }: TPutFlyioVariable) => { + return request.post( + "https://api.fly.io/graphql", + { + query: + "mutation SetAppSecrets($appId: ID!, $secrets: [SecretInput!]!) { setSecrets(input: { appId: $appId, secrets: $secrets }) { app { name } release { version } } }", + variables: { + appId, + secrets: Object.entries(secretMap).map(([key, { value }]) => ({ key, value })) + } + }, + { + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json" + } + } + ); +}; + +const deleteFlyioSecrets = async ({ accessToken, appId, keys }: TDeleteFlyioVariable) => { + return request.post( + "https://api.fly.io/graphql", + { + query: + "mutation UnsetAppSecrets($appId: ID!, $keys: [String!]!) { unsetSecrets(input: { appId: $appId, keys: $keys }) { app { name } release { version } } }", + variables: { + appId, + keys + } + }, + { + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json" + } + } + ); +}; + +export const FlyioSyncFns = { + syncSecrets: async (secretSync: TFlyioSyncWithCredentials, secretMap: TSecretMap) => { + const { + connection, + environment, + destinationConfig: { appId } + } = secretSync; + + const { accessToken } = connection.credentials; + + try { + await putFlyioSecrets({ accessToken, appId, secretMap }); + } catch (error) { + throw new SecretSyncError({ + error + }); + } + + if (secretSync.syncOptions.disableSecretDeletion) return; + + const secrets = await listFlyioSecrets({ accessToken, appId }); + + const keys = secrets.filter( + (secret) => + matchesSchema(secret, environment?.slug || "", secretSync.syncOptions.keySchema) && !(secret in secretMap) + ); + + try { + await deleteFlyioSecrets({ accessToken, appId, keys }); + } catch (error) { + throw new SecretSyncError({ + error + }); + } + }, + removeSecrets: async (secretSync: TFlyioSyncWithCredentials, secretMap: TSecretMap) => { + const { + connection, + destinationConfig: { appId } + } = secretSync; + + const { accessToken } = connection.credentials; + + const secrets = await listFlyioSecrets({ accessToken, appId }); + + const keys = secrets.filter((secret) => secret in secretMap); + + try { + await deleteFlyioSecrets({ accessToken, appId, keys }); + } catch (error) { + throw new SecretSyncError({ + error + }); + } + }, + getSecrets: async (secretSync: TFlyioSyncWithCredentials) => { + throw new Error(`${SECRET_SYNC_NAME_MAP[secretSync.destination]} does not support importing secrets.`); + } +}; diff --git a/backend/src/services/secret-sync/flyio/flyio-sync-schemas.ts b/backend/src/services/secret-sync/flyio/flyio-sync-schemas.ts new file mode 100644 index 000000000..e65af2db0 --- /dev/null +++ b/backend/src/services/secret-sync/flyio/flyio-sync-schemas.ts @@ -0,0 +1,43 @@ +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 FlyioSyncDestinationConfigSchema = z.object({ + appId: z.string().trim().min(1, "App required").describe(SecretSyncs.DESTINATION_CONFIG.FLYIO.appId) +}); + +const FlyioSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false }; + +export const FlyioSyncSchema = BaseSecretSyncSchema(SecretSync.Flyio, FlyioSyncOptionsConfig).extend({ + destination: z.literal(SecretSync.Flyio), + destinationConfig: FlyioSyncDestinationConfigSchema +}); + +export const CreateFlyioSyncSchema = GenericCreateSecretSyncFieldsSchema( + SecretSync.Flyio, + FlyioSyncOptionsConfig +).extend({ + destinationConfig: FlyioSyncDestinationConfigSchema +}); + +export const UpdateFlyioSyncSchema = GenericUpdateSecretSyncFieldsSchema( + SecretSync.Flyio, + FlyioSyncOptionsConfig +).extend({ + destinationConfig: FlyioSyncDestinationConfigSchema.optional() +}); + +export const FlyioSyncListItemSchema = z.object({ + name: z.literal("Fly.io"), + connection: z.literal(AppConnection.Flyio), + destination: z.literal(SecretSync.Flyio), + canImportSecrets: z.literal(false) +}); diff --git a/backend/src/services/secret-sync/flyio/flyio-sync-types.ts b/backend/src/services/secret-sync/flyio/flyio-sync-types.ts new file mode 100644 index 000000000..336639091 --- /dev/null +++ b/backend/src/services/secret-sync/flyio/flyio-sync-types.ts @@ -0,0 +1,32 @@ +import { z } from "zod"; + +import { TFlyioConnection } from "@app/services/app-connection/flyio"; + +import { CreateFlyioSyncSchema, FlyioSyncListItemSchema, FlyioSyncSchema } from "./flyio-sync-schemas"; + +export type TFlyioSync = z.infer; + +export type TFlyioSyncInput = z.infer; + +export type TFlyioSyncListItem = z.infer; + +export type TFlyioSyncWithCredentials = TFlyioSync & { + connection: TFlyioConnection; +}; + +export type TFlyioSecret = { + name: string; +}; + +export type TFlyioListVariables = { + accessToken: string; + appId: string; +}; + +export type TPutFlyioVariable = TFlyioListVariables & { + secretMap: { [key: string]: { value: string } }; +}; + +export type TDeleteFlyioVariable = TFlyioListVariables & { + keys: string[]; +}; diff --git a/backend/src/services/secret-sync/flyio/index.ts b/backend/src/services/secret-sync/flyio/index.ts new file mode 100644 index 000000000..b2fe2b0d5 --- /dev/null +++ b/backend/src/services/secret-sync/flyio/index.ts @@ -0,0 +1,4 @@ +export * from "./flyio-sync-constants"; +export * from "./flyio-sync-fns"; +export * from "./flyio-sync-schemas"; +export * from "./flyio-sync-types"; diff --git a/backend/src/services/secret-sync/secret-sync-enums.ts b/backend/src/services/secret-sync/secret-sync-enums.ts index 3cf940459..85e458feb 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", + Flyio = "flyio" } 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..30589785e 100644 --- a/backend/src/services/secret-sync/secret-sync-fns.ts +++ b/backend/src/services/secret-sync/secret-sync-fns.ts @@ -29,6 +29,7 @@ import { AZURE_APP_CONFIGURATION_SYNC_LIST_OPTION, azureAppConfigurationSyncFact import { AZURE_DEVOPS_SYNC_LIST_OPTION, azureDevOpsSyncFactory } from "./azure-devops"; import { AZURE_KEY_VAULT_SYNC_LIST_OPTION, azureKeyVaultSyncFactory } from "./azure-key-vault"; import { CAMUNDA_SYNC_LIST_OPTION, camundaSyncFactory } from "./camunda"; +import { FLYIO_SYNC_LIST_OPTION, FlyioSyncFns } from "./flyio"; import { GCP_SYNC_LIST_OPTION } from "./gcp"; import { GcpSyncFns } from "./gcp/gcp-sync-fns"; import { HC_VAULT_SYNC_LIST_OPTION, HCVaultSyncFns } from "./hc-vault"; @@ -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.Flyio]: FLYIO_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.Flyio: + return FlyioSyncFns.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.Flyio: + secretMap = await FlyioSyncFns.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.Flyio: + return FlyioSyncFns.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..4e2f50b70 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.Flyio]: "Fly.io" }; 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.Flyio]: AppConnection.Flyio }; 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.Flyio]: 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..192140db5 100644 --- a/backend/src/services/secret-sync/secret-sync-types.ts +++ b/backend/src/services/secret-sync/secret-sync-types.ts @@ -72,6 +72,7 @@ import { TAzureKeyVaultSyncListItem, TAzureKeyVaultSyncWithCredentials } from "./azure-key-vault"; +import { TFlyioSync, TFlyioSyncInput, TFlyioSyncListItem, TFlyioSyncWithCredentials } from "./flyio/flyio-sync-types"; import { TGcpSync, TGcpSyncInput, TGcpSyncListItem, TGcpSyncWithCredentials } from "./gcp"; import { THCVaultSync, @@ -116,7 +117,8 @@ export type TSecretSync = | THCVaultSync | TTeamCitySync | TOCIVaultSync - | TOnePassSync; + | TOnePassSync + | TFlyioSync; export type TSecretSyncWithCredentials = | TAwsParameterStoreSyncWithCredentials @@ -135,7 +137,8 @@ export type TSecretSyncWithCredentials = | THCVaultSyncWithCredentials | TTeamCitySyncWithCredentials | TOCIVaultSyncWithCredentials - | TOnePassSyncWithCredentials; + | TOnePassSyncWithCredentials + | TFlyioSyncWithCredentials; export type TSecretSyncInput = | TAwsParameterStoreSyncInput @@ -154,7 +157,8 @@ export type TSecretSyncInput = | THCVaultSyncInput | TTeamCitySyncInput | TOCIVaultSyncInput - | TOnePassSyncInput; + | TOnePassSyncInput + | TFlyioSyncInput; export type TSecretSyncListItem = | TAwsParameterStoreSyncListItem @@ -173,7 +177,8 @@ export type TSecretSyncListItem = | THCVaultSyncListItem | TTeamCitySyncListItem | TOCIVaultSyncListItem - | TOnePassSyncListItem; + | TOnePassSyncListItem + | TFlyioSyncListItem; export type TSyncOptionsConfig = { canImportSecrets: boolean; diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/FlyioSyncFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/FlyioSyncFields.tsx new file mode 100644 index 000000000..a2a5bf844 --- /dev/null +++ b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/FlyioSyncFields.tsx @@ -0,0 +1,51 @@ +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 } from "@app/components/v2"; +import { TFlyioApp, useFlyioConnectionListApps } from "@app/hooks/api/appConnections/flyio"; +import { SecretSync } from "@app/hooks/api/secretSyncs"; + +import { TSecretSyncForm } from "../schemas"; + +export const FlyioSyncFields = () => { + const { control, setValue } = useFormContext< + TSecretSyncForm & { destination: SecretSync.Flyio } + >(); + + const connectionId = useWatch({ name: "connection.id", control }); + + const { data: apps, isLoading: isAppsLoading } = useFlyioConnectionListApps(connectionId, { + enabled: Boolean(connectionId) + }); + + return ( + <> + { + setValue("destinationConfig.appId", ""); + }} + /> + + ( + + v.id === value) ?? null} + onChange={(option) => onChange((option as SingleValue)?.id ?? null)} + options={apps} + placeholder="Select an app..." + getOptionLabel={(option) => option.name} + 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 71405c448..37976301a 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/SecretSyncDestinationFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/SecretSyncDestinationFields.tsx @@ -11,6 +11,7 @@ import { AzureDevOpsSyncFields } from "./AzureDevOpsSyncFields"; import { AzureKeyVaultSyncFields } from "./AzureKeyVaultSyncFields"; import { CamundaSyncFields } from "./CamundaSyncFields"; import { DatabricksSyncFields } from "./DatabricksSyncFields"; +import { FlyioSyncFields } from "./FlyioSyncFields"; import { GcpSyncFields } from "./GcpSyncFields"; import { GitHubSyncFields } from "./GitHubSyncFields"; import { HCVaultSyncFields } from "./HCVaultSyncFields"; @@ -61,6 +62,8 @@ export const SecretSyncDestinationFields = () => { return ; case SecretSync.OnePass: return ; + case SecretSync.Flyio: + 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..70c2fc498 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.Flyio: AdditionalSyncOptionsFieldsComponent = null; break; default: diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/FlyioSyncReviewFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/FlyioSyncReviewFields.tsx new file mode 100644 index 000000000..9a1a4bf69 --- /dev/null +++ b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/FlyioSyncReviewFields.tsx @@ -0,0 +1,12 @@ +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 FlyioSyncReviewFields = () => { + const { watch } = useFormContext(); + const appId = watch("destinationConfig.appId"); + + return {appId}; +}; diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx index 854555ffb..82d62d19b 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx @@ -20,6 +20,7 @@ import { AzureDevOpsSyncReviewFields } from "./AzureDevOpsSyncReviewFields"; import { AzureKeyVaultSyncReviewFields } from "./AzureKeyVaultSyncReviewFields"; import { CamundaSyncReviewFields } from "./CamundaSyncReviewFields"; import { DatabricksSyncReviewFields } from "./DatabricksSyncReviewFields"; +import { FlyioSyncReviewFields } from "./FlyioSyncReviewFields"; import { GcpSyncReviewFields } from "./GcpSyncReviewFields"; import { GitHubSyncReviewFields } from "./GitHubSyncReviewFields"; import { HCVaultSyncReviewFields } from "./HCVaultSyncReviewFields"; @@ -104,6 +105,9 @@ export const SecretSyncReviewFields = () => { case SecretSync.OnePass: DestinationFieldsComponent = ; break; + case SecretSync.Flyio: + DestinationFieldsComponent = ; + break; default: throw new Error(`Unhandled Destination Review Fields: ${destination}`); } diff --git a/frontend/src/components/secret-syncs/forms/schemas/flyio-sync-destination-schema.ts b/frontend/src/components/secret-syncs/forms/schemas/flyio-sync-destination-schema.ts new file mode 100644 index 000000000..b18081ccc --- /dev/null +++ b/frontend/src/components/secret-syncs/forms/schemas/flyio-sync-destination-schema.ts @@ -0,0 +1,13 @@ +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 FlyioSyncDestinationSchema = BaseSecretSyncSchema().merge( + z.object({ + destination: z.literal(SecretSync.Flyio), + destinationConfig: z.object({ + appId: z.string().trim().min(1, "App ID 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 5dfb643b4..a10863f17 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 @@ -8,6 +8,7 @@ import { AzureDevOpsSyncDestinationSchema } from "./azure-devops-sync-destinatio import { AzureKeyVaultSyncDestinationSchema } from "./azure-key-vault-sync-destination-schema"; import { CamundaSyncDestinationSchema } from "./camunda-sync-destination-schema"; import { DatabricksSyncDestinationSchema } from "./databricks-sync-destination-schema"; +import { FlyioSyncDestinationSchema } from "./flyio-sync-destination-schema"; import { GcpSyncDestinationSchema } from "./gcp-sync-destination-schema"; import { GitHubSyncDestinationSchema } from "./github-sync-destination-schema"; import { HCVaultSyncDestinationSchema } from "./hc-vault-sync-destination-schema"; @@ -35,7 +36,8 @@ const SecretSyncUnionSchema = z.discriminatedUnion("destination", [ HCVaultSyncDestinationSchema, TeamCitySyncDestinationSchema, OCIVaultSyncDestinationSchema, - OnePassSyncDestinationSchema + OnePassSyncDestinationSchema, + FlyioSyncDestinationSchema ]); export const SecretSyncFormSchema = SecretSyncUnionSchema; diff --git a/frontend/src/helpers/secretSyncs.ts b/frontend/src/helpers/secretSyncs.ts index 98a519e33..69d8804f8 100644 --- a/frontend/src/helpers/secretSyncs.ts +++ b/frontend/src/helpers/secretSyncs.ts @@ -60,6 +60,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.Flyio]: AppConnection.Flyio }; export const SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP: Record< diff --git a/frontend/src/hooks/api/appConnections/flyio/types.ts b/frontend/src/hooks/api/appConnections/flyio/types.ts index b3e46f3fd..73345e628 100644 --- a/frontend/src/hooks/api/appConnections/flyio/types.ts +++ b/frontend/src/hooks/api/appConnections/flyio/types.ts @@ -1,4 +1,3 @@ -// TODO(andrey): apps export type TFlyioApp = { id: string; name: string; diff --git a/frontend/src/hooks/api/secretSyncs/enums.ts b/frontend/src/hooks/api/secretSyncs/enums.ts index f381b3fca..17a5e0983 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", + Flyio = "flyio" } export enum SecretSyncStatus { diff --git a/frontend/src/hooks/api/secretSyncs/types/flyio-sync.ts b/frontend/src/hooks/api/secretSyncs/types/flyio-sync.ts new file mode 100644 index 000000000..0717de2c4 --- /dev/null +++ b/frontend/src/hooks/api/secretSyncs/types/flyio-sync.ts @@ -0,0 +1,15 @@ +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 TFlyioSync = TRootSecretSync & { + destination: SecretSync.Flyio; + destinationConfig: { + appId: string; + }; + connection: { + app: AppConnection.Flyio; + name: string; + id: string; + }; +}; diff --git a/frontend/src/hooks/api/secretSyncs/types/index.ts b/frontend/src/hooks/api/secretSyncs/types/index.ts index 4f447f9df..0d5bbdb56 100644 --- a/frontend/src/hooks/api/secretSyncs/types/index.ts +++ b/frontend/src/hooks/api/secretSyncs/types/index.ts @@ -9,6 +9,7 @@ import { TAzureDevOpsSync } from "./azure-devops-sync"; import { TAzureKeyVaultSync } from "./azure-key-vault-sync"; import { TCamundaSync } from "./camunda-sync"; import { TDatabricksSync } from "./databricks-sync"; +import { TFlyioSync } from "./flyio-sync"; import { TGcpSync } from "./gcp-sync"; import { TGitHubSync } from "./github-sync"; import { THCVaultSync } from "./hc-vault-sync"; @@ -43,7 +44,8 @@ export type TSecretSync = | THCVaultSync | TTeamCitySync | TOCIVaultSync - | TOnePassSync; + | TOnePassSync + | TFlyioSync; export type TListSecretSyncs = { secretSyncs: TSecretSync[] }; diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/FlyioSyncDestinationCol.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/FlyioSyncDestinationCol.tsx new file mode 100644 index 000000000..4b69e8348 --- /dev/null +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/FlyioSyncDestinationCol.tsx @@ -0,0 +1,14 @@ +import { TFlyioSync } from "@app/hooks/api/secretSyncs/types/flyio-sync"; + +import { getSecretSyncDestinationColValues } from "../helpers"; +import { SecretSyncTableCell } from "../SecretSyncTableCell"; + +type Props = { + secretSync: TFlyioSync; +}; + +export const FlyioSyncDestinationCol = ({ 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 f342c361c..87c03aacb 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 @@ -8,6 +8,7 @@ import { AzureDevOpsSyncDestinationCol } from "./AzureDevOpsSyncDestinationCol"; import { AzureKeyVaultDestinationSyncCol } from "./AzureKeyVaultDestinationSyncCol"; import { CamundaSyncDestinationCol } from "./CamundaSyncDestinationCol"; import { DatabricksSyncDestinationCol } from "./DatabricksSyncDestinationCol"; +import { FlyioSyncDestinationCol } from "./FlyioSyncDestinationCol"; import { GcpSyncDestinationCol } from "./GcpSyncDestinationCol"; import { GitHubSyncDestinationCol } from "./GitHubSyncDestinationCol"; import { HCVaultSyncDestinationCol } from "./HCVaultSyncDestinationCol"; @@ -58,6 +59,8 @@ export const SecretSyncDestinationCol = ({ secretSync }: Props) => { return ; case SecretSync.AzureDevOps: return ; + case SecretSync.Flyio: + 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..06e9a5a48 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.Flyio: + primaryText = destinationConfig.appId; + secondaryText = "App ID"; + break; default: throw new Error(`Unhandled Destination Col Values ${destination}`); } diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/FlyioSyncDestinationSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/FlyioSyncDestinationSection.tsx new file mode 100644 index 000000000..f5132d04b --- /dev/null +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/FlyioSyncDestinationSection.tsx @@ -0,0 +1,14 @@ +import { GenericFieldLabel } from "@app/components/secret-syncs"; +import { TFlyioSync } from "@app/hooks/api/secretSyncs/types/flyio-sync"; + +type Props = { + secretSync: TFlyioSync; +}; + +export const FlyioSyncDestinationSection = ({ secretSync }: Props) => { + const { + destinationConfig: { appId } + } = secretSync; + + return {appId}; +}; 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..5426c9ce5 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/SecretSyncDestinatonSection.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/SecretSyncDestinatonSection.tsx @@ -18,6 +18,7 @@ import { AzureDevOpsSyncDestinationSection } from "./AzureDevOpsSyncDestinationS import { AzureKeyVaultSyncDestinationSection } from "./AzureKeyVaultSyncDestinationSection"; import { CamundaSyncDestinationSection } from "./CamundaSyncDestinationSection"; import { DatabricksSyncDestinationSection } from "./DatabricksSyncDestinationSection"; +import { FlyioSyncDestinationSection } from "./FlyioSyncDestinationSection"; import { GcpSyncDestinationSection } from "./GcpSyncDestinationSection"; import { GitHubSyncDestinationSection } from "./GitHubSyncDestinationSection"; import { HCVaultSyncDestinationSection } from "./HCVaultSyncDestinationSection"; @@ -93,6 +94,9 @@ export const SecretSyncDestinationSection = ({ secretSync, onEditDestination }: case SecretSync.AzureDevOps: DestinationComponents = ; break; + case SecretSync.Flyio: + 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..85d9d9a64 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.Flyio: AdditionalSyncOptionsComponent = null; break; default: