mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat: chef secret-sync
This commit is contained in:
@@ -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."
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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
|
||||
});
|
||||
@@ -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<SecretSync, (server: Fastif
|
||||
[SecretSync.DigitalOceanAppPlatform]: registerDigitalOceanAppPlatformSyncRouter,
|
||||
[SecretSync.Netlify]: registerNetlifySyncRouter,
|
||||
[SecretSync.Bitbucket]: registerBitbucketSyncRouter,
|
||||
[SecretSync.LaravelForge]: registerLaravelForgeSyncRouter
|
||||
[SecretSync.LaravelForge]: registerLaravelForgeSyncRouter,
|
||||
[SecretSync.Chef]: registerChefSyncRouter
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@ import { AzureKeyVaultSyncListItemSchema, AzureKeyVaultSyncSchema } from "@app/s
|
||||
import { BitbucketSyncListItemSchema, BitbucketSyncSchema } from "@app/services/secret-sync/bitbucket";
|
||||
import { CamundaSyncListItemSchema, CamundaSyncSchema } from "@app/services/secret-sync/camunda";
|
||||
import { ChecklySyncListItemSchema, ChecklySyncSchema } from "@app/services/secret-sync/checkly/checkly-sync-schemas";
|
||||
import { ChefSyncListItemSchema, ChefSyncSchema } from "@app/services/secret-sync/chef";
|
||||
import {
|
||||
CloudflarePagesSyncListItemSchema,
|
||||
CloudflarePagesSyncSchema
|
||||
@@ -86,7 +87,8 @@ const SecretSyncSchema = z.discriminatedUnion("destination", [
|
||||
DigitalOceanAppPlatformSyncSchema,
|
||||
NetlifySyncSchema,
|
||||
BitbucketSyncSchema,
|
||||
LaravelForgeSyncSchema
|
||||
LaravelForgeSyncSchema,
|
||||
ChefSyncSchema
|
||||
]);
|
||||
|
||||
const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
||||
@@ -120,7 +122,8 @@ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
||||
SupabaseSyncListItemSchema,
|
||||
NetlifySyncListItemSchema,
|
||||
BitbucketSyncListItemSchema,
|
||||
LaravelForgeSyncListItemSchema
|
||||
LaravelForgeSyncListItemSchema,
|
||||
ChefSyncListItemSchema
|
||||
]);
|
||||
|
||||
export const registerSecretSyncRouter = async (server: FastifyZodProvider) => {
|
||||
|
||||
@@ -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<TChefDataBag[]> => {
|
||||
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<Record<string, string>>(`${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<TChefDataBag[]> => {
|
||||
export const listChefDataBags = async (appConnection: TChefConnection): Promise<TChefDataBag[]> => {
|
||||
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<TChefDataBagItem[]> => {
|
||||
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<Record<string, string>>(`${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<TChefDataBagItemContent> => {
|
||||
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<TChefDataBagItemContent>(`${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<void> => {
|
||||
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"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -18,15 +18,13 @@ export type TChefConnectionInput = z.infer<typeof CreateChefConnectionSchema> &
|
||||
export type TValidateChefConnectionCredentialsSchema = typeof ValidateChefConnectionCredentialsSchema;
|
||||
|
||||
export type TChefConnectionConfig = DiscriminativePick<TChefConnectionInput, "method" | "app" | "credentials"> & {
|
||||
orgId: string;
|
||||
orgName: string;
|
||||
};
|
||||
|
||||
export type TChefDataBag = {
|
||||
name: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type TChefDataBagItem = {
|
||||
name: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
10
backend/src/services/secret-sync/chef/chef-sync-constants.ts
Normal file
10
backend/src/services/secret-sync/chef/chef-sync-constants.ts
Normal file
@@ -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
|
||||
};
|
||||
143
backend/src/services/secret-sync/chef/chef-sync-fns.ts
Normal file
143
backend/src/services/secret-sync/chef/chef-sync-fns.ts
Normal file
@@ -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<TChefDataBagItemContent> => {
|
||||
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<ChefSecret[]> => {
|
||||
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<string, string | number | boolean>
|
||||
) => {
|
||||
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<TSecretMap> {
|
||||
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);
|
||||
}
|
||||
};
|
||||
46
backend/src/services/secret-sync/chef/chef-sync-schemas.ts
Normal file
46
backend/src/services/secret-sync/chef/chef-sync-schemas.ts
Normal file
@@ -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)
|
||||
});
|
||||
34
backend/src/services/secret-sync/chef/chef-sync-types.ts
Normal file
34
backend/src/services/secret-sync/chef/chef-sync-types.ts
Normal file
@@ -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<typeof ChefSyncListItemSchema>;
|
||||
|
||||
export type TChefSync = z.infer<typeof ChefSyncSchema>;
|
||||
|
||||
export type TChefSyncInput = z.infer<typeof CreateChefSyncSchema>;
|
||||
|
||||
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;
|
||||
};
|
||||
4
backend/src/services/secret-sync/chef/index.ts
Normal file
4
backend/src/services/secret-sync/chef/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from "./chef-sync-constants";
|
||||
export * from "./chef-sync-fns";
|
||||
export * from "./chef-sync-schemas";
|
||||
export * from "./chef-sync-types";
|
||||
@@ -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 {
|
||||
|
||||
@@ -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, TSecretSyncListItem> = {
|
||||
[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}`
|
||||
|
||||
@@ -33,7 +33,8 @@ export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
|
||||
[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<SecretSync, AppConnection> = {
|
||||
@@ -67,7 +68,8 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
||||
[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<SecretSync, SecretSyncPlanType> = {
|
||||
@@ -101,7 +103,8 @@ export const SECRET_SYNC_PLAN_MAP: Record<SecretSync, SecretSyncPlanType> = {
|
||||
[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<SecretSync, string[]> = {
|
||||
@@ -144,7 +147,8 @@ export const SECRET_SYNC_SKIP_FIELDS_MAP: Record<SecretSync, string[]> = {
|
||||
[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<SecretSync, DestinationDupl
|
||||
[SecretSync.DigitalOceanAppPlatform]: defaultDuplicateCheck,
|
||||
[SecretSync.Netlify]: defaultDuplicateCheck,
|
||||
[SecretSync.Bitbucket]: defaultDuplicateCheck,
|
||||
[SecretSync.LaravelForge]: defaultDuplicateCheck
|
||||
[SecretSync.LaravelForge]: defaultDuplicateCheck,
|
||||
[SecretSync.Chef]: defaultDuplicateCheck
|
||||
};
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
TCamundaSyncListItem,
|
||||
TCamundaSyncWithCredentials
|
||||
} from "@app/services/secret-sync/camunda";
|
||||
import { TChefSync, TChefSyncInput, TChefSyncListItem, TChefSyncWithCredentials } from "@app/services/secret-sync/chef";
|
||||
import {
|
||||
TDatabricksSync,
|
||||
TDatabricksSyncInput,
|
||||
@@ -163,6 +164,7 @@ export type TSecretSync =
|
||||
| TGitHubSync
|
||||
| TGcpSync
|
||||
| TAzureKeyVaultSync
|
||||
| TChefSync
|
||||
| TAzureAppConfigurationSync
|
||||
| TAzureDevOpsSync
|
||||
| TDatabricksSync
|
||||
@@ -195,6 +197,7 @@ export type TSecretSyncWithCredentials =
|
||||
| TGitHubSyncWithCredentials
|
||||
| TGcpSyncWithCredentials
|
||||
| TAzureKeyVaultSyncWithCredentials
|
||||
| TChefSyncWithCredentials
|
||||
| TAzureAppConfigurationSyncWithCredentials
|
||||
| TAzureDevOpsSyncWithCredentials
|
||||
| TDatabricksSyncWithCredentials
|
||||
@@ -228,6 +231,7 @@ export type TSecretSyncInput =
|
||||
| TGitHubSyncInput
|
||||
| TGcpSyncInput
|
||||
| TAzureKeyVaultSyncInput
|
||||
| TChefSyncInput
|
||||
| TAzureAppConfigurationSyncInput
|
||||
| TAzureDevOpsSyncInput
|
||||
| TDatabricksSyncInput
|
||||
@@ -261,6 +265,7 @@ export type TSecretSyncListItem =
|
||||
| TGitHubSyncListItem
|
||||
| TGcpSyncListItem
|
||||
| TAzureKeyVaultSyncListItem
|
||||
| TChefSyncListItem
|
||||
| TAzureAppConfigurationSyncListItem
|
||||
| TAzureDevOpsSyncListItem
|
||||
| TDatabricksSyncListItem
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
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 {
|
||||
TChefDataBag,
|
||||
useChefConnectionListDataBags,
|
||||
useChefConnectionListDataBagItems,
|
||||
TChefDataBagItem
|
||||
} from "@app/hooks/api/appConnections/chef";
|
||||
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||
|
||||
import { TSecretSyncForm } from "../schemas";
|
||||
|
||||
export const ChefSyncFields = () => {
|
||||
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 (
|
||||
<>
|
||||
<SecretSyncConnectionField onChange={handleChangeConnection} />
|
||||
|
||||
<Controller
|
||||
name="destinationConfig.dataBagName"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl isError={Boolean(error)} errorText={error?.message} label="Data Bag">
|
||||
<FilterableSelect
|
||||
menuPlacement="top"
|
||||
isLoading={isDataBagsLoading && Boolean(connectionId)}
|
||||
isDisabled={!connectionId}
|
||||
value={dataBags?.find((dataBag) => dataBag.name === value) ?? null}
|
||||
onChange={(option) => {
|
||||
const selectedDataBag = option as SingleValue<TChefDataBag>;
|
||||
onChange(selectedDataBag?.name ?? "");
|
||||
setValue("destinationConfig.dataBagItemName", "");
|
||||
}}
|
||||
options={dataBags}
|
||||
placeholder="Select a data bag..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.name}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="destinationConfig.dataBagItemName"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl isError={Boolean(error)} errorText={error?.message} label="Data Bag Item">
|
||||
<FilterableSelect
|
||||
menuPlacement="top"
|
||||
isLoading={isDataBagItemsLoading && Boolean(connectionId && dataBagName)}
|
||||
isDisabled={!connectionId || !dataBagName}
|
||||
value={dataBagItems?.find((dataBagItem) => dataBagItem.name === value) ?? null}
|
||||
onChange={(option) => {
|
||||
const selectedDataBagItem = option as SingleValue<TChefDataBagItem>;
|
||||
onChange(selectedDataBagItem?.name ?? "");
|
||||
setValue("destinationConfig.dataBagItemName", selectedDataBagItem?.name ?? "");
|
||||
}}
|
||||
options={dataBagItems}
|
||||
placeholder="Select a data bag item..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.name}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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<TSecretSyncForm>();
|
||||
@@ -103,6 +104,8 @@ export const SecretSyncDestinationFields = () => {
|
||||
return <BitbucketSyncFields />;
|
||||
case SecretSync.LaravelForge:
|
||||
return <LaravelForgeSyncFields />;
|
||||
case SecretSync.Chef:
|
||||
return <ChefSyncFields />;
|
||||
default:
|
||||
throw new Error(`Unhandled Destination Config Field: ${destination}`);
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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<TSecretSyncForm & { destination: SecretSync.Chef }>();
|
||||
const dataBagName = watch("destinationConfig.dataBagName");
|
||||
const dataBagItemName = watch("destinationConfig.dataBagItemName");
|
||||
|
||||
return (
|
||||
<>
|
||||
<GenericFieldLabel label="Data Bag">{dataBagName}</GenericFieldLabel>
|
||||
<GenericFieldLabel label="Data Bag Item">{dataBagItemName}</GenericFieldLabel>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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<TSecretSyncForm>();
|
||||
@@ -173,6 +174,9 @@ export const SecretSyncReviewFields = () => {
|
||||
case SecretSync.LaravelForge:
|
||||
DestinationFieldsComponent = <LaravelForgeSyncReviewFields />;
|
||||
break;
|
||||
case SecretSync.Chef:
|
||||
DestinationFieldsComponent = <ChefSyncReviewFields />;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unhandled Destination Review Fields: ${destination}`);
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
})
|
||||
);
|
||||
@@ -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;
|
||||
|
||||
@@ -117,6 +117,10 @@ export const SECRET_SYNC_MAP: Record<SecretSync, { name: string; image: string }
|
||||
[SecretSync.LaravelForge]: {
|
||||
name: "Laravel Forge",
|
||||
image: "Laravel Forge.png"
|
||||
},
|
||||
[SecretSync.Chef]: {
|
||||
name: "Chef",
|
||||
image: "Chef.png"
|
||||
}
|
||||
};
|
||||
|
||||
@@ -151,7 +155,8 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
||||
[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<
|
||||
|
||||
2
frontend/src/hooks/api/appConnections/chef/index.ts
Normal file
2
frontend/src/hooks/api/appConnections/chef/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./queries";
|
||||
export * from "./types";
|
||||
68
frontend/src/hooks/api/appConnections/chef/queries.tsx
Normal file
68
frontend/src/hooks/api/appConnections/chef/queries.tsx
Normal file
@@ -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<typeof chefConnectionKeys.listDataBags>
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: chefConnectionKeys.listDataBags(connectionId),
|
||||
queryFn: async () => {
|
||||
const { data } = await apiRequest.get<TChefDataBag[]>(
|
||||
`/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<typeof chefConnectionKeys.listDataBagItems>
|
||||
>,
|
||||
"queryKey" | "queryFn"
|
||||
>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: chefConnectionKeys.listDataBagItems(connectionId, dataBagName),
|
||||
queryFn: async () => {
|
||||
const params = { dataBagName };
|
||||
const { data } = await apiRequest.get<TChefDataBagItem[]>(
|
||||
`/api/v1/app-connections/chef/${connectionId}/data-bag-items`,
|
||||
{ params }
|
||||
);
|
||||
|
||||
return data;
|
||||
},
|
||||
enabled: Boolean(connectionId && dataBagName),
|
||||
...options
|
||||
});
|
||||
};
|
||||
7
frontend/src/hooks/api/appConnections/chef/types.ts
Normal file
7
frontend/src/hooks/api/appConnections/chef/types.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export type TChefDataBag = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type TChefDataBagItem = {
|
||||
name: string;
|
||||
};
|
||||
@@ -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 {
|
||||
|
||||
16
frontend/src/hooks/api/secretSyncs/types/chef-sync.ts
Normal file
16
frontend/src/hooks/api/secretSyncs/types/chef-sync.ts
Normal file
@@ -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;
|
||||
};
|
||||
};
|
||||
@@ -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[] };
|
||||
|
||||
|
||||
@@ -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 <SecretSyncTableCell primaryText={primaryText} secondaryText={secondaryText} />;
|
||||
};
|
||||
@@ -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 <BitbucketSyncDestinationCol secretSync={secretSync} />;
|
||||
case SecretSync.LaravelForge:
|
||||
return <LaravelForgeSyncDestinationCol secretSync={secretSync} />;
|
||||
case SecretSync.Chef:
|
||||
return <ChefSyncDestinationCol secretSync={secretSync} />;
|
||||
default:
|
||||
throw new Error(
|
||||
`Unhandled Secret Sync Destination Col: ${(secretSync as TSecretSync).destination}`
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<GenericFieldLabel label="Data Bag">{destinationConfig.dataBagName}</GenericFieldLabel>
|
||||
<GenericFieldLabel label="Data Bag Item">
|
||||
{destinationConfig.dataBagItemName}
|
||||
</GenericFieldLabel>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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 = <LaravelForgeSyncDestinationSection secretSync={secretSync} />;
|
||||
break;
|
||||
case SecretSync.Chef:
|
||||
DestinationComponents = <ChefSyncDestinationSection secretSync={secretSync} />;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unhandled Destination Section components: ${destination}`);
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user