From 30c0cfb8c292d8bcda45e0cb28932811deed75f5 Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Thu, 30 Oct 2025 20:06:04 +0530 Subject: [PATCH] feat: chef secret-sync --- backend/src/lib/api-docs/constants.ts | 4 + .../chef-connection-router.ts | 123 ++++++------- .../secret-sync-routers/chef-sync-router.ts | 13 ++ .../routes/v1/secret-sync-routers/index.ts | 4 +- .../secret-sync-routers/secret-sync-router.ts | 7 +- .../chef/chef-connection-fns.ts | 169 +++++++++++++----- .../chef/chef-connection-types.ts | 4 +- .../secret-sync/chef/chef-sync-constants.ts | 10 ++ .../secret-sync/chef/chef-sync-fns.ts | 143 +++++++++++++++ .../secret-sync/chef/chef-sync-schemas.ts | 46 +++++ .../secret-sync/chef/chef-sync-types.ts | 34 ++++ .../src/services/secret-sync/chef/index.ts | 4 + .../services/secret-sync/secret-sync-enums.ts | 3 +- .../services/secret-sync/secret-sync-fns.ts | 14 +- .../services/secret-sync/secret-sync-maps.ts | 15 +- .../services/secret-sync/secret-sync-types.ts | 5 + .../ChefSyncFields.tsx | 97 ++++++++++ .../SecretSyncDestinationFields.tsx | 3 + .../SecretSyncOptionsFields.tsx | 1 + .../ChefSyncReviewFields.tsx | 18 ++ .../SecretSyncReviewFields.tsx | 4 + .../schemas/chef-sync-destination-schema.ts | 14 ++ .../forms/schemas/secret-sync-schema.ts | 4 +- frontend/src/helpers/secretSyncs.ts | 7 +- .../hooks/api/appConnections/chef/index.ts | 2 + .../hooks/api/appConnections/chef/queries.tsx | 68 +++++++ .../hooks/api/appConnections/chef/types.ts | 7 + frontend/src/hooks/api/secretSyncs/enums.ts | 3 +- .../hooks/api/secretSyncs/types/chef-sync.ts | 16 ++ .../src/hooks/api/secretSyncs/types/index.ts | 4 +- .../ChefSyncDestinationCol.tsx | 14 ++ .../SecretSyncDestinationCol.tsx | 3 + .../SecretSyncTable/helpers/index.ts | 4 + .../ChefSyncDestinationSection.tsx | 19 ++ .../SecretSyncDestinatonSection.tsx | 4 + .../SecretSyncOptionsSection.tsx | 1 + 36 files changed, 767 insertions(+), 124 deletions(-) create mode 100644 backend/src/server/routes/v1/secret-sync-routers/chef-sync-router.ts create mode 100644 backend/src/services/secret-sync/chef/chef-sync-constants.ts create mode 100644 backend/src/services/secret-sync/chef/chef-sync-fns.ts create mode 100644 backend/src/services/secret-sync/chef/chef-sync-schemas.ts create mode 100644 backend/src/services/secret-sync/chef/chef-sync-types.ts create mode 100644 backend/src/services/secret-sync/chef/index.ts create mode 100644 frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/ChefSyncFields.tsx create mode 100644 frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/ChefSyncReviewFields.tsx create mode 100644 frontend/src/components/secret-syncs/forms/schemas/chef-sync-destination-schema.ts create mode 100644 frontend/src/hooks/api/appConnections/chef/index.ts create mode 100644 frontend/src/hooks/api/appConnections/chef/queries.tsx create mode 100644 frontend/src/hooks/api/appConnections/chef/types.ts create mode 100644 frontend/src/hooks/api/secretSyncs/types/chef-sync.ts create mode 100644 frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/ChefSyncDestinationCol.tsx create mode 100644 frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/ChefSyncDestinationSection.tsx diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index cd0782483..2aa14adff 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -2626,6 +2626,10 @@ export const SecretSyncs = { siteName: "The name of the Netlify site to sync secrets to.", siteId: "The ID of the Netlify site to sync secrets to.", context: "The Netlify context to sync secrets to." + }, + CHEF: { + dataBagName: "The name of the Chef data bag to sync secrets to.", + dataBagItemName: "The name of the Chef data bag item to sync secrets to." } } }; diff --git a/backend/src/server/routes/v1/app-connection-routers/chef-connection-router.ts b/backend/src/server/routes/v1/app-connection-routers/chef-connection-router.ts index d57bd354f..6bfd83391 100644 --- a/backend/src/server/routes/v1/app-connection-routers/chef-connection-router.ts +++ b/backend/src/server/routes/v1/app-connection-routers/chef-connection-router.ts @@ -1,15 +1,15 @@ -// import z from "zod"; +import z from "zod"; -// import { readLimit } from "@app/server/config/rateLimiter"; -// import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +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 { CreateChefConnectionSchema, SanitizedChefConnectionSchema, UpdateChefConnectionSchema } from "@app/services/app-connection/chef"; +import { AuthMode } from "@app/services/auth/auth-type"; -// import { AuthMode } from "@app/services/auth/auth-type"; import { registerAppConnectionEndpoints } from "./app-connection-endpoints"; export const registerChefConnectionRouter = async (server: FastifyZodProvider) => { @@ -21,64 +21,65 @@ export const registerChefConnectionRouter = async (server: FastifyZodProvider) = updateSchema: UpdateChefConnectionSchema }); - // server.route({ - // method: "GET", - // url: `/:connectionId/data-bags`, - // 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 dataBags = await server.services.appConnection.chef.listDataBags(connectionId, req.permission); + server.route({ + method: "GET", + url: `/:connectionId/data-bags`, + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + connectionId: z.string().uuid() + }), + response: { + 200: z + .object({ + name: z.string() + }) + .array() + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const { connectionId } = req.params; + const dataBags = await server.services.appConnection.chef.listDataBags(connectionId, req.permission); - // return dataBags; - // } - // }); + return dataBags; + } + }); - // server.route({ - // method: "GET", - // url: `/:connectionId/data-bags/:dataBagName/items`, - // config: { - // rateLimit: readLimit - // }, - // schema: { - // params: z.object({ - // connectionId: z.string().uuid(), - // dataBagName: z.string() - // }), - // response: { - // 200: z - // .object({ - // id: z.string(), - // name: z.string() - // }) - // .array() - // } - // }, - // onRequest: verifyAuth([AuthMode.JWT]), - // handler: async (req) => { - // const { connectionId, dataBagName } = req.params; - // const dataBagItems = await server.services.appConnection.chef.listDataBagItems( - // connectionId, - // dataBagName, - // req.permission - // ); + server.route({ + method: "GET", + url: `/:connectionId/data-bag-items`, + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + connectionId: z.string().uuid() + }), + querystring: z.object({ + dataBagName: z.string() + }), + response: { + 200: z + .object({ + name: z.string() + }) + .array() + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const { connectionId } = req.params; + const { dataBagName } = req.query; + const dataBagItems = await server.services.appConnection.chef.listDataBagItems( + connectionId, + dataBagName, + req.permission + ); - // return dataBagItems; - // } - // }); + return dataBagItems; + } + }); }; diff --git a/backend/src/server/routes/v1/secret-sync-routers/chef-sync-router.ts b/backend/src/server/routes/v1/secret-sync-routers/chef-sync-router.ts new file mode 100644 index 000000000..6972a9b70 --- /dev/null +++ b/backend/src/server/routes/v1/secret-sync-routers/chef-sync-router.ts @@ -0,0 +1,13 @@ +import { ChefSyncSchema, CreateChefSyncSchema, UpdateChefSyncSchema } from "@app/services/secret-sync/chef"; +import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; + +import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints"; + +export const registerChefSyncRouter = async (server: FastifyZodProvider) => + registerSyncSecretsEndpoints({ + destination: SecretSync.Chef, + server, + responseSchema: ChefSyncSchema, + createSchema: CreateChefSyncSchema, + updateSchema: UpdateChefSyncSchema + }); 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 e778dbd7c..b6d866577 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 { registerAzureKeyVaultSyncRouter } from "./azure-key-vault-sync-router"; import { registerBitbucketSyncRouter } from "./bitbucket-sync-router"; import { registerCamundaSyncRouter } from "./camunda-sync-router"; import { registerChecklySyncRouter } from "./checkly-sync-router"; +import { registerChefSyncRouter } from "./chef-sync-router"; import { registerCloudflarePagesSyncRouter } from "./cloudflare-pages-sync-router"; import { registerCloudflareWorkersSyncRouter } from "./cloudflare-workers-sync-router"; import { registerDatabricksSyncRouter } from "./databricks-sync-router"; @@ -65,5 +66,6 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record { diff --git a/backend/src/services/app-connection/chef/chef-connection-fns.ts b/backend/src/services/app-connection/chef/chef-connection-fns.ts index a60325cc3..a224a82f7 100644 --- a/backend/src/services/app-connection/chef/chef-connection-fns.ts +++ b/backend/src/services/app-connection/chef/chef-connection-fns.ts @@ -7,9 +7,10 @@ import { removeTrailingSlash } from "@app/lib/fn"; import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator"; import { IntegrationUrls } from "@app/services/integration-auth/integration-list"; +import { TChefDataBagItemContent } from "../../secret-sync/chef/chef-sync-types"; import { AppConnection } from "../app-connection-enums"; import { ChefConnectionMethod } from "./chef-connection-enums"; -import { TChefConnection, TChefConnectionConfig, TChefDataBag } from "./chef-connection-types"; +import { TChefConnection, TChefConnectionConfig, TChefDataBag, TChefDataBagItem } from "./chef-connection-types"; export const getChefServerUrl = async (serverUrl?: string) => { const chefServerUrl = serverUrl ? removeTrailingSlash(serverUrl) : IntegrationUrls.CHEF_API_URL; @@ -120,40 +121,6 @@ export const getChefConnectionListItem = () => { }; }; -export const listChefDataBags = async (appConnection: TChefConnection): Promise => { - const { - credentials: { serverUrl, userName, privateKey } - } = appConnection; - - try { - const path = "/data"; - const body = ""; - - const hostServerUrl = await getChefServerUrl(serverUrl); - - const headers = getChefAuthHeaders("GET", path, body, userName, privateKey); - - const res = await request.get>(`${hostServerUrl}${path}`, { - headers - }); - - // Chef returns data bags as an object with keys being data bag names - return Object.keys(res.data).map((name) => ({ - name, - id: name - })); - } catch (error) { - if (error instanceof AxiosError) { - // throw new BadRequestError({ - // message: `Failed to list Chef data bags: ${error.response?.data?.error || error.message}` - // }); - } - throw new BadRequestError({ - message: "Unable to list Chef data bags" - }); - } -}; - export const validateChefConnectionCredentials = async (config: TChefConnectionConfig) => { const { credentials: inputCredentials } = config; @@ -181,16 +148,13 @@ export const validateChefConnectionCredentials = async (config: TChefConnectionC return inputCredentials; }; -export const listChefDataBagItems = async ( - appConnection: TChefConnection, - dataBagName: string -): Promise => { +export const listChefDataBags = async (appConnection: TChefConnection): Promise => { const { - credentials: { serverUrl, userName, privateKey } + credentials: { serverUrl, userName, privateKey, orgName } } = appConnection; try { - const path = `/data/${dataBagName}`; + const path = `/organizations/${orgName}/data`; const body = ""; const hostServerUrl = await getChefServerUrl(serverUrl); @@ -201,10 +165,43 @@ export const listChefDataBagItems = async ( headers }); - // Chef returns data bag items as an object with keys being item names return Object.keys(res.data).map((name) => ({ - name, - id: name + name + })); + } catch (error) { + if (error instanceof AxiosError) { + throw new BadRequestError({ + message: `Failed to list Chef data bags: ${error.message || "Unknown error"}` + }); + } + throw new BadRequestError({ + message: "Unable to list Chef data bags" + }); + } +}; + +export const listChefDataBagItems = async ( + appConnection: TChefConnection, + dataBagName: string +): Promise => { + const { + credentials: { serverUrl, userName, privateKey, orgName } + } = appConnection; + + try { + const path = `/organizations/${orgName}/data/${dataBagName}`; + const body = ""; + + const hostServerUrl = await getChefServerUrl(serverUrl); + + const headers = getChefAuthHeaders("GET", path, body, userName, privateKey); + + const res = await request.get>(`${hostServerUrl}${path}`, { + headers + }); + + return Object.keys(res.data).map((name) => ({ + name })); } catch (error) { if (error instanceof AxiosError) { @@ -217,3 +214,87 @@ export const listChefDataBagItems = async ( }); } }; + +type TGetChefDataBagItem = { + serverUrl?: string; + userName: string; + privateKey: string; + orgName: string; + dataBagName: string; + dataBagItemName: string; +}; + +export const getChefDataBagItem = async ({ + serverUrl, + userName, + privateKey, + orgName, + dataBagName, + dataBagItemName +}: TGetChefDataBagItem): Promise => { + try { + const path = `/organizations/${orgName}/data/${dataBagName}/${dataBagItemName}`; + const body = ""; + + const hostServerUrl = await getChefServerUrl(serverUrl); + + const headers = getChefAuthHeaders("GET", path, body, userName, privateKey); + + const res = await request.get(`${hostServerUrl}${path}`, { + headers + }); + + return res.data; + } catch (error) { + if (error instanceof AxiosError) { + throw new BadRequestError({ + message: `Failed to get Chef data bag item: ${error.message || "Unknown error"}` + }); + } + throw new BadRequestError({ + message: "Unable to get Chef data bag item" + }); + } +}; + +type TUpdateChefDataBagItem = { + serverUrl?: string; + userName: string; + privateKey: string; + orgName: string; + dataBagName: string; + dataBagItemName: string; + data: TChefDataBagItemContent; +}; + +export const updateChefDataBagItem = async ({ + serverUrl, + userName, + privateKey, + orgName, + dataBagName, + dataBagItemName, + data +}: TUpdateChefDataBagItem): Promise => { + try { + const path = `/organizations/${orgName}/data/${dataBagName}/${dataBagItemName}`; + const body = JSON.stringify(data); + + const hostServerUrl = await getChefServerUrl(serverUrl); + + const headers = getChefAuthHeaders("PUT", path, body, userName, privateKey); + + await request.put(`${hostServerUrl}${path}`, data, { + headers + }); + } catch (error) { + if (error instanceof AxiosError) { + throw new BadRequestError({ + message: `Failed to update Chef data bag item: ${error.message || "Unknown error"}` + }); + } + throw new BadRequestError({ + message: "Unable to update Chef data bag item" + }); + } +}; diff --git a/backend/src/services/app-connection/chef/chef-connection-types.ts b/backend/src/services/app-connection/chef/chef-connection-types.ts index 7996026af..33f0d5793 100644 --- a/backend/src/services/app-connection/chef/chef-connection-types.ts +++ b/backend/src/services/app-connection/chef/chef-connection-types.ts @@ -18,15 +18,13 @@ export type TChefConnectionInput = z.infer & export type TValidateChefConnectionCredentialsSchema = typeof ValidateChefConnectionCredentialsSchema; export type TChefConnectionConfig = DiscriminativePick & { - orgId: string; + orgName: string; }; export type TChefDataBag = { name: string; - id: string; }; export type TChefDataBagItem = { name: string; - id: string; }; diff --git a/backend/src/services/secret-sync/chef/chef-sync-constants.ts b/backend/src/services/secret-sync/chef/chef-sync-constants.ts new file mode 100644 index 000000000..567b25bbe --- /dev/null +++ b/backend/src/services/secret-sync/chef/chef-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 CHEF_SYNC_LIST_OPTION: TSecretSyncListItem = { + name: "Chef", + destination: SecretSync.Chef, + connection: AppConnection.Chef, + canImportSecrets: true +}; diff --git a/backend/src/services/secret-sync/chef/chef-sync-fns.ts b/backend/src/services/secret-sync/chef/chef-sync-fns.ts new file mode 100644 index 000000000..33e89035e --- /dev/null +++ b/backend/src/services/secret-sync/chef/chef-sync-fns.ts @@ -0,0 +1,143 @@ +import { getChefDataBagItem, updateChefDataBagItem } from "@app/services/app-connection/chef"; +import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns"; +import { TSecretMap } from "@app/services/secret-sync/secret-sync-types"; + +import { ChefSecret, TChefDataBagItemContent, TChefSyncWithCredentials, TGetChefSecrets } from "./chef-sync-types"; + +const getChefSecretsRaw = async ({ + serverUrl, + userName, + privateKey, + orgName, + dataBagName, + dataBagItemName +}: TGetChefSecrets): Promise => { + const dataBagItem = await getChefDataBagItem({ + serverUrl, + userName, + privateKey, + orgName, + dataBagName, + dataBagItemName + }); + + // Ensure the data bag item has an id field + if (!dataBagItem.id) { + dataBagItem.id = dataBagItemName; + } + + return dataBagItem; +}; + +const getChefSecrets = async (secretSync: TChefSyncWithCredentials): Promise => { + const { + connection, + destinationConfig: { dataBagName, dataBagItemName } + } = secretSync; + + const { serverUrl, userName, privateKey, orgName } = connection.credentials; + + const dataBagItem = await getChefSecretsRaw({ + serverUrl, + orgName, + userName, + privateKey, + dataBagName, + dataBagItemName + }); + + // Convert data bag item to key-value pairs + // Exclude the 'id' field as it's metadata + const secrets: ChefSecret[] = []; + Object.entries(dataBagItem).forEach(([key, value]) => { + if (key !== "id" && value !== null && value !== undefined) { + secrets.push({ key, value: String(value) }); + } + }); + + return secrets; +}; + +const updateChefSecrets = async ( + secretSync: TChefSyncWithCredentials, + secrets: Record +) => { + const { + connection, + destinationConfig: { dataBagName, dataBagItemName } + } = secretSync; + + const { serverUrl, userName, privateKey, orgName } = connection.credentials; + + // Chef data bag items must have an 'id' field + const dataBagItemContent: TChefDataBagItemContent = { + id: dataBagItemName, + ...secrets + }; + + await updateChefDataBagItem({ + serverUrl, + orgName, + userName, + privateKey, + dataBagName, + dataBagItemName, + data: dataBagItemContent + }); +}; + +export const ChefSyncFns = { + async syncSecrets(secretSync: TChefSyncWithCredentials, secretMap: TSecretMap) { + const { + environment, + syncOptions: { disableSecretDeletion, keySchema } + } = secretSync; + + const secrets = await getChefSecrets(secretSync); + + // Create a map of the existing secrets + const updatedSecretsMap = new Map(secrets.map((secret) => [secret.key, secret.value])); + + // Add/update new secrets + for (const [key, { value }] of Object.entries(secretMap)) { + updatedSecretsMap.set(key, value); + } + + // Delete secrets if not disabled + if (!disableSecretDeletion) { + secrets.forEach((secret) => { + if (!matchesSchema(secret.key, environment?.slug || "", keySchema)) return; + + if (!secretMap[secret.key]) { + updatedSecretsMap.delete(secret.key); + } + }); + } + + // Convert map to object for Chef API + const updatedSecrets = Object.fromEntries(updatedSecretsMap.entries()); + + await updateChefSecrets(secretSync, updatedSecrets); + }, + + async getSecrets(secretSync: TChefSyncWithCredentials): Promise { + const secrets = await getChefSecrets(secretSync); + return Object.fromEntries(secrets.map((secret) => [secret.key, { value: secret.value }])); + }, + + async removeSecrets(secretSync: TChefSyncWithCredentials, secretMap: TSecretMap) { + const existingSecrets = await getChefSecrets(secretSync); + + const newSecrets = existingSecrets.filter((secret) => !Object.hasOwn(secretMap, secret.key)); + + // If nothing changed, return early + if (newSecrets.length === existingSecrets.length) { + return; + } + + // Convert to object for Chef API + const updatedSecrets = Object.fromEntries(newSecrets.map((secret) => [secret.key, secret.value])); + + await updateChefSecrets(secretSync, updatedSecrets); + } +}; diff --git a/backend/src/services/secret-sync/chef/chef-sync-schemas.ts b/backend/src/services/secret-sync/chef/chef-sync-schemas.ts new file mode 100644 index 000000000..c2e09011e --- /dev/null +++ b/backend/src/services/secret-sync/chef/chef-sync-schemas.ts @@ -0,0 +1,46 @@ +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 ChefSyncDestinationConfigSchema = z.object({ + dataBagName: z + .string() + .min(1, "Data Bag Name is required") + .max(256, "Data Bag Name cannot exceed 256 characters") + .describe(SecretSyncs.DESTINATION_CONFIG.CHEF.dataBagName), + dataBagItemName: z + .string() + .min(1, "Data Bag Item Name is required") + .max(256, "Data Bag Item Name cannot exceed 256 characters") + .describe(SecretSyncs.DESTINATION_CONFIG.CHEF.dataBagItemName) +}); + +const ChefSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true }; + +export const ChefSyncSchema = BaseSecretSyncSchema(SecretSync.Chef, ChefSyncOptionsConfig).extend({ + destination: z.literal(SecretSync.Chef), + destinationConfig: ChefSyncDestinationConfigSchema +}); + +export const CreateChefSyncSchema = GenericCreateSecretSyncFieldsSchema(SecretSync.Chef, ChefSyncOptionsConfig).extend({ + destinationConfig: ChefSyncDestinationConfigSchema +}); + +export const UpdateChefSyncSchema = GenericUpdateSecretSyncFieldsSchema(SecretSync.Chef, ChefSyncOptionsConfig).extend({ + destinationConfig: ChefSyncDestinationConfigSchema.optional() +}); + +export const ChefSyncListItemSchema = z.object({ + name: z.literal("Chef"), + connection: z.literal(AppConnection.Chef), + destination: z.literal(SecretSync.Chef), + canImportSecrets: z.literal(true) +}); diff --git a/backend/src/services/secret-sync/chef/chef-sync-types.ts b/backend/src/services/secret-sync/chef/chef-sync-types.ts new file mode 100644 index 000000000..2ad35ba87 --- /dev/null +++ b/backend/src/services/secret-sync/chef/chef-sync-types.ts @@ -0,0 +1,34 @@ +import z from "zod"; + +import { TChefConnection } from "@app/services/app-connection/chef"; + +import { ChefSyncListItemSchema, ChefSyncSchema, CreateChefSyncSchema } from "./chef-sync-schemas"; + +export type TChefSyncListItem = z.infer; + +export type TChefSync = z.infer; + +export type TChefSyncInput = z.infer; + +export type TChefSyncWithCredentials = TChefSync & { + connection: TChefConnection; +}; + +export type TGetChefSecrets = { + serverUrl?: string; + userName: string; + privateKey: string; + orgName: string; + dataBagName: string; + dataBagItemName: string; +}; + +export type TChefDataBagItemContent = { + id: string; + [key: string]: string | number | boolean | null; +}; + +export type ChefSecret = { + key: string; + value: string; +}; diff --git a/backend/src/services/secret-sync/chef/index.ts b/backend/src/services/secret-sync/chef/index.ts new file mode 100644 index 000000000..c8599c867 --- /dev/null +++ b/backend/src/services/secret-sync/chef/index.ts @@ -0,0 +1,4 @@ +export * from "./chef-sync-constants"; +export * from "./chef-sync-fns"; +export * from "./chef-sync-schemas"; +export * from "./chef-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 235b3db3a..a9625b53f 100644 --- a/backend/src/services/secret-sync/secret-sync-enums.ts +++ b/backend/src/services/secret-sync/secret-sync-enums.ts @@ -29,7 +29,8 @@ export enum SecretSync { DigitalOceanAppPlatform = "digital-ocean-app-platform", Netlify = "netlify", Bitbucket = "bitbucket", - LaravelForge = "laravel-forge" + LaravelForge = "laravel-forge", + Chef = "chef" } 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 85fc27250..46f53fda6 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 { BITBUCKET_SYNC_LIST_OPTION, BitbucketSyncFns } from "./bitbucket"; import { CAMUNDA_SYNC_LIST_OPTION, camundaSyncFactory } from "./camunda"; import { CHECKLY_SYNC_LIST_OPTION } from "./checkly/checkly-sync-constants"; import { ChecklySyncFns } from "./checkly/checkly-sync-fns"; +import { CHEF_SYNC_LIST_OPTION, ChefSyncFns } from "./chef"; import { CLOUDFLARE_PAGES_SYNC_LIST_OPTION } from "./cloudflare-pages/cloudflare-pages-constants"; import { CloudflarePagesSyncFns } from "./cloudflare-pages/cloudflare-pages-fns"; import { CLOUDFLARE_WORKERS_SYNC_LIST_OPTION, CloudflareWorkersSyncFns } from "./cloudflare-workers"; @@ -49,8 +50,7 @@ import { HC_VAULT_SYNC_LIST_OPTION, HCVaultSyncFns } from "./hc-vault"; import { HEROKU_SYNC_LIST_OPTION, HerokuSyncFns } from "./heroku"; import { HUMANITEC_SYNC_LIST_OPTION } from "./humanitec"; import { HumanitecSyncFns } from "./humanitec/humanitec-sync-fns"; -import { LARAVEL_FORGE_SYNC_LIST_OPTION } from "./laravel-forge"; -import { LaravelForgeSyncFns } from "./laravel-forge/laravel-forge-sync-fns"; +import { LARAVEL_FORGE_SYNC_LIST_OPTION, LaravelForgeSyncFns } from "./laravel-forge"; import { NETLIFY_SYNC_LIST_OPTION, NetlifySyncFns } from "./netlify"; import { RAILWAY_SYNC_LIST_OPTION } from "./railway/railway-sync-constants"; import { RailwaySyncFns } from "./railway/railway-sync-fns"; @@ -94,7 +94,8 @@ const SECRET_SYNC_LIST_OPTIONS: Record = { [SecretSync.DigitalOceanAppPlatform]: DIGITAL_OCEAN_APP_PLATFORM_SYNC_LIST_OPTION, [SecretSync.Netlify]: NETLIFY_SYNC_LIST_OPTION, [SecretSync.Bitbucket]: BITBUCKET_SYNC_LIST_OPTION, - [SecretSync.LaravelForge]: LARAVEL_FORGE_SYNC_LIST_OPTION + [SecretSync.LaravelForge]: LARAVEL_FORGE_SYNC_LIST_OPTION, + [SecretSync.Chef]: CHEF_SYNC_LIST_OPTION }; export const listSecretSyncOptions = () => { @@ -282,6 +283,8 @@ export const SecretSyncFns = { return BitbucketSyncFns.syncSecrets(secretSync, schemaSecretMap); case SecretSync.LaravelForge: return LaravelForgeSyncFns.syncSecrets(secretSync, schemaSecretMap); + case SecretSync.Chef: + return ChefSyncFns.syncSecrets(secretSync, schemaSecretMap); default: throw new Error( `Unhandled sync destination for sync secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}` @@ -401,6 +404,9 @@ export const SecretSyncFns = { case SecretSync.LaravelForge: secretMap = await LaravelForgeSyncFns.getSecrets(secretSync); break; + case SecretSync.Chef: + secretMap = await ChefSyncFns.getSecrets(secretSync); + break; default: throw new Error( `Unhandled sync destination for get secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}` @@ -496,6 +502,8 @@ export const SecretSyncFns = { return BitbucketSyncFns.removeSecrets(secretSync, schemaSecretMap); case SecretSync.LaravelForge: return LaravelForgeSyncFns.removeSecrets(secretSync, schemaSecretMap); + case SecretSync.Chef: + return ChefSyncFns.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 0ec8aede0..e82d4f077 100644 --- a/backend/src/services/secret-sync/secret-sync-maps.ts +++ b/backend/src/services/secret-sync/secret-sync-maps.ts @@ -33,7 +33,8 @@ export const SECRET_SYNC_NAME_MAP: Record = { [SecretSync.DigitalOceanAppPlatform]: "Digital Ocean App Platform", [SecretSync.Netlify]: "Netlify", [SecretSync.Bitbucket]: "Bitbucket", - [SecretSync.LaravelForge]: "Laravel Forge" + [SecretSync.LaravelForge]: "Laravel Forge", + [SecretSync.Chef]: "Chef" }; export const SECRET_SYNC_CONNECTION_MAP: Record = { @@ -67,7 +68,8 @@ export const SECRET_SYNC_CONNECTION_MAP: Record = { [SecretSync.DigitalOceanAppPlatform]: AppConnection.DigitalOcean, [SecretSync.Netlify]: AppConnection.Netlify, [SecretSync.Bitbucket]: AppConnection.Bitbucket, - [SecretSync.LaravelForge]: AppConnection.LaravelForge + [SecretSync.LaravelForge]: AppConnection.LaravelForge, + [SecretSync.Chef]: AppConnection.Chef }; export const SECRET_SYNC_PLAN_MAP: Record = { @@ -101,7 +103,8 @@ export const SECRET_SYNC_PLAN_MAP: Record = { [SecretSync.DigitalOceanAppPlatform]: SecretSyncPlanType.Regular, [SecretSync.Netlify]: SecretSyncPlanType.Regular, [SecretSync.Bitbucket]: SecretSyncPlanType.Regular, - [SecretSync.LaravelForge]: SecretSyncPlanType.Regular + [SecretSync.LaravelForge]: SecretSyncPlanType.Regular, + [SecretSync.Chef]: SecretSyncPlanType.Regular }; export const SECRET_SYNC_SKIP_FIELDS_MAP: Record = { @@ -144,7 +147,8 @@ export const SECRET_SYNC_SKIP_FIELDS_MAP: Record = { [SecretSync.DigitalOceanAppPlatform]: ["appName"], [SecretSync.Netlify]: ["accountName", "siteName"], [SecretSync.Bitbucket]: [], - [SecretSync.LaravelForge]: [] + [SecretSync.LaravelForge]: [], + [SecretSync.Chef]: [] }; const defaultDuplicateCheck: DestinationDuplicateCheckFn = () => true; @@ -204,5 +208,6 @@ export const DESTINATION_DUPLICATE_CHECK_MAP: Record { + const { control, setValue } = useFormContext< + TSecretSyncForm & { destination: SecretSync.Chef } + >(); + + const connectionId = useWatch({ name: "connection.id", control }); + const dataBagName = useWatch({ name: "destinationConfig.dataBagName", control }); + const dataBagItemName = useWatch({ name: "destinationConfig.dataBagItemName", control }); + + const { data: dataBags, isLoading: isDataBagsLoading } = useChefConnectionListDataBags( + connectionId, + { + enabled: Boolean(connectionId) + } + ); + + const { data: dataBagItems, isLoading: isDataBagItemsLoading } = + useChefConnectionListDataBagItems(connectionId, dataBagName, { + enabled: Boolean(connectionId && dataBagName) + }); + + console.log({ dataBags, dataBagItems }); + + const handleChangeConnection = () => { + setValue("destinationConfig.dataBagName", ""); + setValue("destinationConfig.dataBagItemName", ""); + }; + + return ( + <> + + + ( + + dataBag.name === value) ?? null} + onChange={(option) => { + const selectedDataBag = option as SingleValue; + onChange(selectedDataBag?.name ?? ""); + setValue("destinationConfig.dataBagItemName", ""); + }} + options={dataBags} + placeholder="Select a data bag..." + getOptionLabel={(option) => option.name} + getOptionValue={(option) => option.name} + /> + + )} + /> + + ( + + dataBagItem.name === value) ?? null} + onChange={(option) => { + const selectedDataBagItem = option as SingleValue; + onChange(selectedDataBagItem?.name ?? ""); + setValue("destinationConfig.dataBagItemName", selectedDataBagItem?.name ?? ""); + }} + options={dataBagItems} + placeholder="Select a data bag item..." + getOptionLabel={(option) => option.name} + getOptionValue={(option) => option.name} + /> + + )} + /> + + ); +}; diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/SecretSyncDestinationFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/SecretSyncDestinationFields.tsx index 61ba54369..5137b7d39 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/SecretSyncDestinationFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/SecretSyncDestinationFields.tsx @@ -34,6 +34,7 @@ import { TerraformCloudSyncFields } from "./TerraformCloudSyncFields"; import { VercelSyncFields } from "./VercelSyncFields"; import { WindmillSyncFields } from "./WindmillSyncFields"; import { ZabbixSyncFields } from "./ZabbixSyncFields"; +import { ChefSyncFields } from "./ChefSyncFields"; export const SecretSyncDestinationFields = () => { const { watch } = useFormContext(); @@ -103,6 +104,8 @@ export const SecretSyncDestinationFields = () => { return ; case SecretSync.LaravelForge: return ; + case SecretSync.Chef: + 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 f66cadaea..dbd5f743a 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx @@ -70,6 +70,7 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => { case SecretSync.Netlify: case SecretSync.Bitbucket: case SecretSync.LaravelForge: + case SecretSync.Chef: AdditionalSyncOptionsFieldsComponent = null; break; default: diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/ChefSyncReviewFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/ChefSyncReviewFields.tsx new file mode 100644 index 000000000..95f5310a2 --- /dev/null +++ b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/ChefSyncReviewFields.tsx @@ -0,0 +1,18 @@ +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 ChefSyncReviewFields = () => { + const { watch } = useFormContext(); + const dataBagName = watch("destinationConfig.dataBagName"); + const dataBagItemName = watch("destinationConfig.dataBagItemName"); + + return ( + <> + {dataBagName} + {dataBagItemName} + + ); +}; diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx index 8c8ea523b..78f365a65 100644 --- a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx +++ b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx @@ -47,6 +47,7 @@ import { TerraformCloudSyncReviewFields } from "./TerraformCloudSyncReviewFields import { VercelSyncReviewFields } from "./VercelSyncReviewFields"; import { WindmillSyncReviewFields } from "./WindmillSyncReviewFields"; import { ZabbixSyncReviewFields } from "./ZabbixSyncReviewFields"; +import { ChefSyncReviewFields } from "./ChefSyncReviewFields"; export const SecretSyncReviewFields = () => { const { watch } = useFormContext(); @@ -173,6 +174,9 @@ export const SecretSyncReviewFields = () => { case SecretSync.LaravelForge: DestinationFieldsComponent = ; break; + case SecretSync.Chef: + DestinationFieldsComponent = ; + break; default: throw new Error(`Unhandled Destination Review Fields: ${destination}`); } diff --git a/frontend/src/components/secret-syncs/forms/schemas/chef-sync-destination-schema.ts b/frontend/src/components/secret-syncs/forms/schemas/chef-sync-destination-schema.ts new file mode 100644 index 000000000..b69781ce1 --- /dev/null +++ b/frontend/src/components/secret-syncs/forms/schemas/chef-sync-destination-schema.ts @@ -0,0 +1,14 @@ +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 ChefSyncDestinationSchema = BaseSecretSyncSchema().merge( + z.object({ + destination: z.literal(SecretSync.Chef), + destinationConfig: z.object({ + dataBagName: z.string().trim().min(1, "Data Bag Name required"), + dataBagItemName: z.string().trim().min(1, "Data Bag Item Name 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 5ebc38184..85c8289a9 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 @@ -31,6 +31,7 @@ import { TerraformCloudSyncDestinationSchema } from "./terraform-cloud-destinati import { VercelSyncDestinationSchema } from "./vercel-sync-destination-schema"; import { WindmillSyncDestinationSchema } from "./windmill-sync-destination-schema"; import { ZabbixSyncDestinationSchema } from "./zabbix-sync-destination-schema"; +import { ChefSyncDestinationSchema } from "./chef-sync-destination-schema"; const SecretSyncUnionSchema = z.discriminatedUnion("destination", [ AwsParameterStoreSyncDestinationSchema, @@ -63,7 +64,8 @@ const SecretSyncUnionSchema = z.discriminatedUnion("destination", [ DigitalOceanAppPlatformSyncDestinationSchema, NetlifySyncDestinationSchema, BitbucketSyncDestinationSchema, - LaravelForgeSyncDestinationSchema + LaravelForgeSyncDestinationSchema, + ChefSyncDestinationSchema ]); export const SecretSyncFormSchema = SecretSyncUnionSchema; diff --git a/frontend/src/helpers/secretSyncs.ts b/frontend/src/helpers/secretSyncs.ts index 12cae6ea6..f2cae0be5 100644 --- a/frontend/src/helpers/secretSyncs.ts +++ b/frontend/src/helpers/secretSyncs.ts @@ -117,6 +117,10 @@ export const SECRET_SYNC_MAP: Record = { [SecretSync.DigitalOceanAppPlatform]: AppConnection.DigitalOcean, [SecretSync.Netlify]: AppConnection.Netlify, [SecretSync.Bitbucket]: AppConnection.Bitbucket, - [SecretSync.LaravelForge]: AppConnection.LaravelForge + [SecretSync.LaravelForge]: AppConnection.LaravelForge, + [SecretSync.Chef]: AppConnection.Chef }; export const SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP: Record< diff --git a/frontend/src/hooks/api/appConnections/chef/index.ts b/frontend/src/hooks/api/appConnections/chef/index.ts new file mode 100644 index 000000000..2c1906d36 --- /dev/null +++ b/frontend/src/hooks/api/appConnections/chef/index.ts @@ -0,0 +1,2 @@ +export * from "./queries"; +export * from "./types"; diff --git a/frontend/src/hooks/api/appConnections/chef/queries.tsx b/frontend/src/hooks/api/appConnections/chef/queries.tsx new file mode 100644 index 000000000..f24a39eed --- /dev/null +++ b/frontend/src/hooks/api/appConnections/chef/queries.tsx @@ -0,0 +1,68 @@ +import { useQuery, UseQueryOptions } from "@tanstack/react-query"; + +import { apiRequest } from "@app/config/request"; +import { appConnectionKeys } from "@app/hooks/api/appConnections"; + +import { TChefDataBag, TChefDataBagItem } from "./types"; + +const chefConnectionKeys = { + all: [...appConnectionKeys.all, "chef"] as const, + listDataBags: (connectionId: string) => + [...chefConnectionKeys.all, "data-bags", connectionId] as const, + listDataBagItems: (connectionId: string, dataBagName: string) => + [...chefConnectionKeys.all, "data-bag-items", connectionId, dataBagName] as const +}; + +export const useChefConnectionListDataBags = ( + connectionId: string, + options?: Omit< + UseQueryOptions< + TChefDataBag[], + unknown, + TChefDataBag[], + ReturnType + >, + "queryKey" | "queryFn" + > +) => { + return useQuery({ + queryKey: chefConnectionKeys.listDataBags(connectionId), + queryFn: async () => { + const { data } = await apiRequest.get( + `/api/v1/app-connections/chef/${connectionId}/data-bags` + ); + + return data; + }, + ...options + }); +}; + +export const useChefConnectionListDataBagItems = ( + connectionId: string, + dataBagName: string, + options?: Omit< + UseQueryOptions< + TChefDataBagItem[], + unknown, + TChefDataBagItem[], + ReturnType + >, + "queryKey" | "queryFn" + > +) => { + return useQuery({ + queryKey: chefConnectionKeys.listDataBagItems(connectionId, dataBagName), + queryFn: async () => { + const params = { dataBagName }; + const { data } = await apiRequest.get( + `/api/v1/app-connections/chef/${connectionId}/data-bag-items`, + { params } + ); + + return data; + }, + enabled: Boolean(connectionId && dataBagName), + ...options + }); +}; diff --git a/frontend/src/hooks/api/appConnections/chef/types.ts b/frontend/src/hooks/api/appConnections/chef/types.ts new file mode 100644 index 000000000..fd87d47c7 --- /dev/null +++ b/frontend/src/hooks/api/appConnections/chef/types.ts @@ -0,0 +1,7 @@ +export type TChefDataBag = { + name: string; +}; + +export type TChefDataBagItem = { + name: string; +}; diff --git a/frontend/src/hooks/api/secretSyncs/enums.ts b/frontend/src/hooks/api/secretSyncs/enums.ts index efcc04b6d..d34876930 100644 --- a/frontend/src/hooks/api/secretSyncs/enums.ts +++ b/frontend/src/hooks/api/secretSyncs/enums.ts @@ -29,7 +29,8 @@ export enum SecretSync { DigitalOceanAppPlatform = "digital-ocean-app-platform", Netlify = "netlify", Bitbucket = "bitbucket", - LaravelForge = "laravel-forge" + LaravelForge = "laravel-forge", + Chef = "chef" } export enum SecretSyncStatus { diff --git a/frontend/src/hooks/api/secretSyncs/types/chef-sync.ts b/frontend/src/hooks/api/secretSyncs/types/chef-sync.ts new file mode 100644 index 000000000..6bd3d7ca2 --- /dev/null +++ b/frontend/src/hooks/api/secretSyncs/types/chef-sync.ts @@ -0,0 +1,16 @@ +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 TChefSync = TRootSecretSync & { + destination: SecretSync.Chef; + destinationConfig: { + dataBagName: string; + dataBagItemName: string; + }; + connection: { + app: AppConnection.Chef; + 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 3cab195bd..577ec7fc4 100644 --- a/frontend/src/hooks/api/secretSyncs/types/index.ts +++ b/frontend/src/hooks/api/secretSyncs/types/index.ts @@ -32,6 +32,7 @@ import { TTerraformCloudSync } from "./terraform-cloud-sync"; import { TVercelSync } from "./vercel-sync"; import { TWindmillSync } from "./windmill-sync"; import { TZabbixSync } from "./zabbix-sync"; +import { TChefSync } from "./chef-sync"; export type TSecretSyncOption = { name: string; @@ -71,7 +72,8 @@ export type TSecretSync = | TDigitalOceanAppPlatformSync | TNetlifySync | TBitbucketSync - | TLaravelForgeSync; + | TLaravelForgeSync + | TChefSync; export type TListSecretSyncs = { secretSyncs: TSecretSync[] }; diff --git a/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/ChefSyncDestinationCol.tsx b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/ChefSyncDestinationCol.tsx new file mode 100644 index 000000000..cd1b2742e --- /dev/null +++ b/frontend/src/pages/secret-manager/IntegrationsListPage/components/SecretSyncsTab/SecretSyncTable/SecretSyncDestinationCol/ChefSyncDestinationCol.tsx @@ -0,0 +1,14 @@ +import { TChefSync } from "@app/hooks/api/secretSyncs/types/chef-sync"; + +import { getSecretSyncDestinationColValues } from "../helpers"; +import { SecretSyncTableCell } from "../SecretSyncTableCell"; + +type Props = { + secretSync: TChefSync; +}; + +export const ChefSyncDestinationCol = ({ 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 1e4df7351..d1abd6e45 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 @@ -31,6 +31,7 @@ import { TerraformCloudSyncDestinationCol } from "./TerraformCloudSyncDestinatio import { VercelSyncDestinationCol } from "./VercelSyncDestinationCol"; import { WindmillSyncDestinationCol } from "./WindmillSyncDestinationCol"; import { ZabbixSyncDestinationCol } from "./ZabbixSyncDestinationCol"; +import { ChefSyncDestinationCol } from "./ChefSyncDestinationCol"; type Props = { secretSync: TSecretSync; @@ -100,6 +101,8 @@ export const SecretSyncDestinationCol = ({ secretSync }: Props) => { return ; case SecretSync.LaravelForge: return ; + case SecretSync.Chef: + 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 c4a3f2a37..559936ce2 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 @@ -198,6 +198,10 @@ export const getSecretSyncDestinationColValues = (secretSync: TSecretSync) => { primaryText = destinationConfig.siteName || destinationConfig.siteId; secondaryText = destinationConfig.orgName || destinationConfig.orgSlug; break; + case SecretSync.Chef: + primaryText = destinationConfig.dataBagName; + secondaryText = destinationConfig.dataBagItemName; + break; default: throw new Error(`Unhandled Destination Col Values ${destination}`); } diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/ChefSyncDestinationSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/ChefSyncDestinationSection.tsx new file mode 100644 index 000000000..c5e230878 --- /dev/null +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/ChefSyncDestinationSection.tsx @@ -0,0 +1,19 @@ +import { GenericFieldLabel } from "@app/components/secret-syncs"; +import { TChefSync } from "@app/hooks/api/secretSyncs/types/chef-sync"; + +type Props = { + secretSync: TChefSync; +}; + +export const ChefSyncDestinationSection = ({ secretSync }: Props) => { + const { destinationConfig } = secretSync; + + return ( + <> + {destinationConfig.dataBagName} + + {destinationConfig.dataBagItemName} + + + ); +}; 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 78e00d8fa..cda641cdb 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/SecretSyncDestinatonSection.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/SecretSyncDestinatonSection.tsx @@ -42,6 +42,7 @@ import { TerraformCloudSyncDestinationSection } from "./TerraformCloudSyncDestin import { VercelSyncDestinationSection } from "./VercelSyncDestinationSection"; import { WindmillSyncDestinationSection } from "./WindmillSyncDestinationSection"; import { ZabbixSyncDestinationSection } from "./ZabbixSyncDestinationSection"; +import { ChefSyncDestinationSection } from "./ChefSyncDestinationSection"; type Props = { secretSync: TSecretSync; @@ -152,6 +153,9 @@ export const SecretSyncDestinationSection = ({ secretSync, onEditDestination }: case SecretSync.LaravelForge: DestinationComponents = ; break; + case SecretSync.Chef: + 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 46ef832dd..ece9dfd45 100644 --- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx +++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx @@ -73,6 +73,7 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) = case SecretSync.Netlify: case SecretSync.Bitbucket: case SecretSync.LaravelForge: + case SecretSync.Chef: AdditionalSyncOptionsComponent = null; break; default: