feature: databricks sync

This commit is contained in:
Scott Wilson
2025-02-10 18:38:27 -08:00
parent 4e48ab1eeb
commit 4de8888843
105 changed files with 1548 additions and 173 deletions

View File

@@ -1718,36 +1718,40 @@ export const SecretSyncs = {
SYNC_OPTIONS: (destination: SecretSync) => {
const destinationName = SECRET_SYNC_NAME_MAP[destination];
return {
INITIAL_SYNC_BEHAVIOR: `Specify how Infisical should resolve the initial sync to the ${destinationName} destination.`,
PREPEND_PREFIX: `Optionally prepend a prefix to your secrets' keys when syncing to ${destinationName}.`,
APPEND_SUFFIX: `Optionally append a suffix to your secrets' keys when syncing to ${destinationName}.`
initialSyncBehavior: `Specify how Infisical should resolve the initial sync to the ${destinationName} destination.`
};
},
DESTINATION_CONFIG: {
AWS_PARAMETER_STORE: {
REGION: "The AWS region to sync secrets to.",
PATH: "The Parameter Store path to sync secrets to."
region: "The AWS region to sync secrets to.",
path: "The Parameter Store path to sync secrets to."
},
AWS_SECRETS_MANAGER: {
REGION: "The AWS region to sync secrets to.",
MAPPING_BEHAVIOR:
"How secrets from Infisical should be mapped to AWS Secrets Manager; one-to-one or many-to-one.",
SECRET_NAME: "The secret name in AWS Secrets Manager to sync to when using mapping behavior many-to-one."
region: "The AWS region to sync secrets to.",
mappingBehavior: "How secrets from Infisical should be mapped to AWS Secrets Manager; one-to-one or many-to-one.",
secretName: "The secret name in AWS Secrets Manager to sync to when using mapping behavior many-to-one."
},
GITHUB: {
ORG: "The name of the GitHub organization.",
OWNER: "The name of the GitHub account owner of the repository.",
REPO: "The name of the GitHub repository.",
ENV: "The name of the GitHub environment."
scope: "The GitHub scope that secrets should be synced to",
org: "The name of the GitHub organization.",
owner: "The name of the GitHub account owner of the repository.",
repo: "The name of the GitHub repository.",
env: "The name of the GitHub environment."
},
AZURE_KEY_VAULT: {
VAULT_BASE_URL:
"The base URL of the Azure Key Vault to sync secrets to. Example: https://example.vault.azure.net/"
vaultBaseUrl: "The base URL of the Azure Key Vault to sync secrets to. Example: https://example.vault.azure.net/"
},
AZURE_APP_CONFIGURATION: {
CONFIGURATION_URL:
configurationUrl:
"The URL of the Azure App Configuration to sync secrets to. Example: https://example.azconfig.io/",
LABEL: "An optional label to assign to secrets created in Azure App Configuration."
label: "An optional label to assign to secrets created in Azure App Configuration."
},
GCP: {
scope: "The Google project scope that secrets should be synced to.",
projectId: "The ID of the Google project secrets should be synced to."
},
DATABRICKS: {
scope: "The Databricks secret scope that secrets should be synced to."
}
}
};

View File

@@ -12,6 +12,10 @@ import {
AzureKeyVaultConnectionListItemSchema,
SanitizedAzureKeyVaultConnectionSchema
} from "@app/services/app-connection/azure-key-vault";
import {
DatabricksConnectionListItemSchema,
SanitizedDatabricksConnectionSchema
} from "@app/services/app-connection/databricks";
import { GcpConnectionListItemSchema, SanitizedGcpConnectionSchema } from "@app/services/app-connection/gcp";
import { GitHubConnectionListItemSchema, SanitizedGitHubConnectionSchema } from "@app/services/app-connection/github";
import { AuthMode } from "@app/services/auth/auth-type";
@@ -22,7 +26,8 @@ const SanitizedAppConnectionSchema = z.union([
...SanitizedGitHubConnectionSchema.options,
...SanitizedGcpConnectionSchema.options,
...SanitizedAzureKeyVaultConnectionSchema.options,
...SanitizedAzureAppConfigurationConnectionSchema.options
...SanitizedAzureAppConfigurationConnectionSchema.options,
...SanitizedDatabricksConnectionSchema.options
]);
const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
@@ -30,7 +35,8 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
GitHubConnectionListItemSchema,
GcpConnectionListItemSchema,
AzureKeyVaultConnectionListItemSchema,
AzureAppConfigurationConnectionListItemSchema
AzureAppConfigurationConnectionListItemSchema,
DatabricksConnectionListItemSchema
]);
export const registerAppConnectionRouter = async (server: FastifyZodProvider) => {

View File

@@ -0,0 +1,54 @@
import { z } from "zod";
import { readLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
CreateDatabricksConnectionSchema,
SanitizedDatabricksConnectionSchema,
UpdateDatabricksConnectionSchema
} from "@app/services/app-connection/databricks";
import { AuthMode } from "@app/services/auth/auth-type";
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
export const registerDatabricksConnectionRouter = async (server: FastifyZodProvider) => {
registerAppConnectionEndpoints({
app: AppConnection.Databricks,
server,
sanitizedResponseSchema: SanitizedDatabricksConnectionSchema,
createSchema: CreateDatabricksConnectionSchema,
updateSchema: UpdateDatabricksConnectionSchema
});
// The below endpoints are not exposed and for Infisical App use
server.route({
method: "GET",
url: `/:connectionId/secret-scopes`,
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
connectionId: z.string().uuid()
}),
response: {
200: z.object({
secretScopes: z.object({ name: z.string() }).array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { connectionId } = req.params;
const secretScopes = await server.services.appConnection.databricks.listSecretScopes(
connectionId,
req.permission
);
return { secretScopes };
}
});
};

View File

@@ -41,7 +41,7 @@ export const registerGitHubConnectionRouter = async (server: FastifyZodProvider)
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { connectionId } = req.params;
@@ -67,7 +67,7 @@ export const registerGitHubConnectionRouter = async (server: FastifyZodProvider)
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { connectionId } = req.params;
@@ -97,7 +97,7 @@ export const registerGitHubConnectionRouter = async (server: FastifyZodProvider)
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { connectionId } = req.params;
const { repo, owner } = req.query;

View File

@@ -3,6 +3,7 @@ import { AppConnection } from "@app/services/app-connection/app-connection-enums
import { registerAwsConnectionRouter } from "./aws-connection-router";
import { registerAzureAppConfigurationConnectionRouter } from "./azure-app-configuration-connection-router";
import { registerAzureKeyVaultConnectionRouter } from "./azure-key-vault-connection-router";
import { registerDatabricksConnectionRouter } from "./databricks-connection-router";
import { registerGcpConnectionRouter } from "./gcp-connection-router";
import { registerGitHubConnectionRouter } from "./github-connection-router";
@@ -14,5 +15,6 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
[AppConnection.GitHub]: registerGitHubConnectionRouter,
[AppConnection.GCP]: registerGcpConnectionRouter,
[AppConnection.AzureKeyVault]: registerAzureKeyVaultConnectionRouter,
[AppConnection.AzureAppConfiguration]: registerAzureAppConfigurationConnectionRouter
[AppConnection.AzureAppConfiguration]: registerAzureAppConfigurationConnectionRouter,
[AppConnection.Databricks]: registerDatabricksConnectionRouter
};

View File

@@ -0,0 +1,17 @@
import {
CreateDatabricksSyncSchema,
DatabricksSyncSchema,
UpdateDatabricksSyncSchema
} from "@app/services/secret-sync/databricks";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
export const registerDatabricksSyncRouter = async (server: FastifyZodProvider) =>
registerSyncSecretsEndpoints({
destination: SecretSync.Databricks,
server,
responseSchema: DatabricksSyncSchema,
createSchema: CreateDatabricksSyncSchema,
updateSchema: UpdateDatabricksSyncSchema
});

View File

@@ -1,3 +1,4 @@
import { registerDatabricksSyncRouter } from "@app/server/routes/v1/secret-sync-routers/databricks-sync-router";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { registerAwsParameterStoreSyncRouter } from "./aws-parameter-store-sync-router";
@@ -15,5 +16,6 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record<SecretSync, (server: Fastif
[SecretSync.GitHub]: registerGitHubSyncRouter,
[SecretSync.GCPSecretManager]: registerGcpSyncRouter,
[SecretSync.AzureKeyVault]: registerAzureKeyVaultSyncRouter,
[SecretSync.AzureAppConfiguration]: registerAzureAppConfigurationSyncRouter
[SecretSync.AzureAppConfiguration]: registerAzureAppConfigurationSyncRouter,
[SecretSync.Databricks]: registerDatabricksSyncRouter
};

View File

@@ -18,6 +18,7 @@ import {
AzureAppConfigurationSyncSchema
} from "@app/services/secret-sync/azure-app-configuration";
import { AzureKeyVaultSyncListItemSchema, AzureKeyVaultSyncSchema } from "@app/services/secret-sync/azure-key-vault";
import { DatabricksSyncListItemSchema, DatabricksSyncSchema } from "@app/services/secret-sync/databricks";
import { GcpSyncListItemSchema, GcpSyncSchema } from "@app/services/secret-sync/gcp";
import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret-sync/github";
@@ -27,7 +28,8 @@ const SecretSyncSchema = z.discriminatedUnion("destination", [
GitHubSyncSchema,
GcpSyncSchema,
AzureKeyVaultSyncSchema,
AzureAppConfigurationSyncSchema
AzureAppConfigurationSyncSchema,
DatabricksSyncSchema
]);
const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
@@ -36,7 +38,8 @@ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
GitHubSyncListItemSchema,
GcpSyncListItemSchema,
AzureKeyVaultSyncListItemSchema,
AzureAppConfigurationSyncListItemSchema
AzureAppConfigurationSyncListItemSchema,
DatabricksSyncListItemSchema
]);
export const registerSecretSyncRouter = async (server: FastifyZodProvider) => {

View File

@@ -1,6 +1,7 @@
export enum AppConnection {
GitHub = "github",
AWS = "aws",
Databricks = "databricks",
GCP = "gcp",
AzureKeyVault = "azure-key-vault",
AzureAppConfiguration = "azure-app-configuration"

View File

@@ -5,12 +5,17 @@ import { TAppConnectionServiceFactoryDep } from "@app/services/app-connection/ap
import { TAppConnection, TAppConnectionConfig } from "@app/services/app-connection/app-connection-types";
import {
AwsConnectionMethod,
getAwsAppConnectionListItem,
getAwsConnectionListItem,
validateAwsConnectionCredentials
} from "@app/services/app-connection/aws";
import {
DatabricksConnectionMethod,
getDatabricksConnectionListItem,
validateDatabricksConnectionCredentials
} from "@app/services/app-connection/databricks";
import {
GcpConnectionMethod,
getGcpAppConnectionListItem,
getGcpConnectionListItem,
validateGcpConnectionCredentials
} from "@app/services/app-connection/gcp";
import {
@@ -33,11 +38,12 @@ import {
export const listAppConnectionOptions = () => {
return [
getAwsAppConnectionListItem(),
getAwsConnectionListItem(),
getGitHubConnectionListItem(),
getGcpAppConnectionListItem(),
getGcpConnectionListItem(),
getAzureKeyVaultConnectionListItem(),
getAzureAppConfigurationConnectionListItem()
getAzureAppConfigurationConnectionListItem(),
getDatabricksConnectionListItem()
].sort((a, b) => a.name.localeCompare(b.name));
};
@@ -90,6 +96,8 @@ export const validateAppConnectionCredentials = async (
switch (app) {
case AppConnection.AWS:
return validateAwsConnectionCredentials(appConnection);
case AppConnection.Databricks:
return validateDatabricksConnectionCredentials(appConnection);
case AppConnection.GitHub:
return validateGitHubConnectionCredentials(appConnection);
case AppConnection.GCP:
@@ -118,6 +126,8 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
return "Assume Role";
case GcpConnectionMethod.ServiceAccountImpersonation:
return "Service Account Impersonation";
case DatabricksConnectionMethod.ServicePrincipal:
return "Service Principal";
default:
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
throw new Error(`Unhandled App Connection Method: ${method}`);

View File

@@ -5,5 +5,6 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
[AppConnection.GitHub]: "GitHub",
[AppConnection.GCP]: "GCP",
[AppConnection.AzureKeyVault]: "Azure Key Vault",
[AppConnection.AzureAppConfiguration]: "Azure App Configuration"
[AppConnection.AzureAppConfiguration]: "Azure App Configuration",
[AppConnection.Databricks]: "Databricks"
};

View File

@@ -23,6 +23,8 @@ import {
TValidateAppConnectionCredentials
} from "@app/services/app-connection/app-connection-types";
import { ValidateAwsConnectionCredentialsSchema } from "@app/services/app-connection/aws";
import { ValidateDatabricksConnectionCredentialsSchema } from "@app/services/app-connection/databricks";
import { databricksConnectionService } from "@app/services/app-connection/databricks/databricks-connection-service";
import { ValidateGitHubConnectionCredentialsSchema } from "@app/services/app-connection/github";
import { githubConnectionService } from "@app/services/app-connection/github/github-connection-service";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
@@ -46,7 +48,8 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
[AppConnection.GitHub]: ValidateGitHubConnectionCredentialsSchema,
[AppConnection.GCP]: ValidateGcpConnectionCredentialsSchema,
[AppConnection.AzureKeyVault]: ValidateAzureKeyVaultConnectionCredentialsSchema,
[AppConnection.AzureAppConfiguration]: ValidateAzureAppConfigurationConnectionCredentialsSchema
[AppConnection.AzureAppConfiguration]: ValidateAzureAppConfigurationConnectionCredentialsSchema,
[AppConnection.Databricks]: ValidateDatabricksConnectionCredentialsSchema
};
export const appConnectionServiceFactory = ({
@@ -365,6 +368,7 @@ export const appConnectionServiceFactory = ({
connectAppConnectionById,
listAvailableAppConnectionsForUser,
github: githubConnectionService(connectAppConnectionById),
gcp: gcpConnectionService(connectAppConnectionById)
gcp: gcpConnectionService(connectAppConnectionById),
databricks: databricksConnectionService(connectAppConnectionById, appConnectionDAL, kmsService)
};
};

View File

@@ -4,6 +4,12 @@ import {
TAwsConnectionInput,
TValidateAwsConnectionCredentials
} from "@app/services/app-connection/aws";
import {
TDatabricksConnection,
TDatabricksConnectionConfig,
TDatabricksConnectionInput,
TValidateDatabricksConnectionCredentials
} from "@app/services/app-connection/databricks";
import {
TGitHubConnection,
TGitHubConnectionConfig,
@@ -31,6 +37,7 @@ export type TAppConnection = { id: string } & (
| TGcpConnection
| TAzureKeyVaultConnection
| TAzureAppConfigurationConnection
| TDatabricksConnection
);
export type TAppConnectionInput = { id: string } & (
@@ -39,6 +46,7 @@ export type TAppConnectionInput = { id: string } & (
| TGcpConnectionInput
| TAzureKeyVaultConnectionInput
| TAzureAppConfigurationConnectionInput
| TDatabricksConnectionInput
);
export type TCreateAppConnectionDTO = Pick<
@@ -55,11 +63,13 @@ export type TAppConnectionConfig =
| TGitHubConnectionConfig
| TGcpConnectionConfig
| TAzureKeyVaultConnectionConfig
| TAzureAppConfigurationConnectionConfig;
| TAzureAppConfigurationConnectionConfig
| TDatabricksConnectionConfig;
export type TValidateAppConnectionCredentials =
| TValidateAwsConnectionCredentials
| TValidateGitHubConnectionCredentials
| TValidateGcpConnectionCredentials
| TValidateAzureKeyVaultConnectionCredentials
| TValidateAzureAppConfigurationConnectionCredentials;
| TValidateAzureAppConfigurationConnectionCredentials
| TValidateDatabricksConnectionCredentials;

View File

@@ -9,7 +9,7 @@ import { AppConnection, AWSRegion } from "@app/services/app-connection/app-conne
import { AwsConnectionMethod } from "./aws-connection-enums";
import { TAwsConnectionConfig } from "./aws-connection-types";
export const getAwsAppConnectionListItem = () => {
export const getAwsConnectionListItem = () => {
const { INF_APP_CONNECTION_AWS_ACCESS_KEY_ID } = getConfig();
return {

View File

@@ -0,0 +1,3 @@
export enum DatabricksConnectionMethod {
ServicePrincipal = "service-principal"
}

View File

@@ -0,0 +1,92 @@
import { request } from "@app/lib/config/request";
import { BadRequestError } from "@app/lib/errors";
import { removeTrailingSlash } from "@app/lib/fn";
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { encryptAppConnectionCredentials } from "@app/services/app-connection/app-connection-fns";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { DatabricksConnectionMethod } from "./databricks-connection-enums";
import {
TAuthorizeDatabricksConnection,
TDatabricksConnection,
TDatabricksConnectionConfig
} from "./databricks-connection-types";
export const getDatabricksConnectionListItem = () => {
return {
name: "Databricks" as const,
app: AppConnection.Databricks as const,
methods: Object.values(DatabricksConnectionMethod) as [DatabricksConnectionMethod.ServicePrincipal]
};
};
const authorizeDatabricksConnection = async ({
clientId,
clientSecret,
workspaceUrl
}: Pick<TDatabricksConnection["credentials"], "workspaceUrl" | "clientId" | "clientSecret">) => {
const { data } = await request.post<TAuthorizeDatabricksConnection>(
`${removeTrailingSlash(workspaceUrl)}/oidc/v1/token`,
"grant_type=client_credentials&scope=all-apis",
{
auth: {
username: clientId,
password: clientSecret
},
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
}
);
return { accessToken: data.access_token, expiresAt: data.expires_in * 1000 + Date.now() };
};
export const getDatabricksConnectionAccessToken = async (
{ id, orgId, credentials }: TDatabricksConnection,
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
) => {
const { clientSecret, clientId, workspaceUrl, accessToken, expiresAt } = credentials;
// get new token if less than 10 minutes from expiry
if (Date.now() < expiresAt - 10_000) {
return accessToken;
}
const authData = await authorizeDatabricksConnection({ clientId, clientSecret, workspaceUrl });
const updatedCredentials: TDatabricksConnection["credentials"] = {
...credentials,
...authData
};
const encryptedCredentials = await encryptAppConnectionCredentials({
credentials: updatedCredentials,
orgId,
kmsService
});
await appConnectionDAL.updateById(id, { encryptedCredentials });
return authData.accessToken;
};
export const validateDatabricksConnectionCredentials = async (appConnection: TDatabricksConnectionConfig) => {
const { credentials } = appConnection;
try {
const { accessToken, expiresAt } = await authorizeDatabricksConnection(appConnection.credentials);
return {
...credentials,
accessToken,
expiresAt
};
} catch (e: unknown) {
throw new BadRequestError({
message: `Unable to validate connection - verify credentials`
});
}
};

View File

@@ -0,0 +1,77 @@
import { z } from "zod";
import { AppConnections } from "@app/lib/api-docs";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
BaseAppConnectionSchema,
GenericCreateAppConnectionFieldsSchema,
GenericUpdateAppConnectionFieldsSchema
} from "@app/services/app-connection/app-connection-schemas";
import { DatabricksConnectionMethod } from "./databricks-connection-enums";
export const DatabricksConnectionServicePrincipalInputCredentialsSchema = z.object({
clientId: z.string().trim().min(1, "Client ID required"),
clientSecret: z.string().trim().min(1, "Client Secret required"),
workspaceUrl: z.string().trim().url().min(1, "Workspace URL required")
});
export const DatabricksConnectionServicePrincipalOutputCredentialsSchema = z
.object({
accessToken: z.string(),
expiresAt: z.number()
})
.merge(DatabricksConnectionServicePrincipalInputCredentialsSchema);
const BaseDatabricksConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.Databricks) });
export const DatabricksConnectionSchema = z.intersection(
BaseDatabricksConnectionSchema,
z.discriminatedUnion("method", [
z.object({
method: z.literal(DatabricksConnectionMethod.ServicePrincipal),
credentials: DatabricksConnectionServicePrincipalOutputCredentialsSchema
})
])
);
export const SanitizedDatabricksConnectionSchema = z.discriminatedUnion("method", [
BaseDatabricksConnectionSchema.extend({
method: z.literal(DatabricksConnectionMethod.ServicePrincipal),
credentials: DatabricksConnectionServicePrincipalOutputCredentialsSchema.pick({
clientId: true,
workspaceUrl: true
})
})
]);
export const ValidateDatabricksConnectionCredentialsSchema = z.discriminatedUnion("method", [
z.object({
method: z
.literal(DatabricksConnectionMethod.ServicePrincipal)
.describe(AppConnections?.CREATE(AppConnection.Databricks).method),
credentials: DatabricksConnectionServicePrincipalInputCredentialsSchema.describe(
AppConnections.CREATE(AppConnection.Databricks).credentials
)
})
]);
export const CreateDatabricksConnectionSchema = ValidateDatabricksConnectionCredentialsSchema.and(
GenericCreateAppConnectionFieldsSchema(AppConnection.Databricks)
);
export const UpdateDatabricksConnectionSchema = z
.object({
credentials: DatabricksConnectionServicePrincipalInputCredentialsSchema.optional().describe(
AppConnections.UPDATE(AppConnection.Databricks).credentials
)
})
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Databricks));
export const DatabricksConnectionListItemSchema = z.object({
name: z.literal("Databricks"),
app: z.literal(AppConnection.Databricks),
// the below is preferable but currently breaks with our zod to json schema parser
// methods: z.tuple([z.literal(AwsConnectionMethod.ServicePrincipal), z.literal(AwsConnectionMethod.AccessKey)]),
methods: z.nativeEnum(DatabricksConnectionMethod).array()
});

View File

@@ -0,0 +1,60 @@
import { request } from "@app/lib/config/request";
import { removeTrailingSlash } from "@app/lib/fn";
import { OrgServiceActor } from "@app/lib/types";
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { getDatabricksConnectionAccessToken } from "@app/services/app-connection/databricks/databricks-connection-fns";
import {
TDatabricksConnection,
TDatabricksListSecretScopesResponse
} from "@app/services/app-connection/databricks/databricks-connection-types";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
type TGetAppConnectionFunc = (
app: AppConnection,
connectionId: string,
actor: OrgServiceActor
) => Promise<TDatabricksConnection>;
const listDatabricksSecretScopes = async (
appConnection: TDatabricksConnection,
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
) => {
const {
credentials: { workspaceUrl }
} = appConnection;
const accessToken = await getDatabricksConnectionAccessToken(appConnection, appConnectionDAL, kmsService);
const { data } = await request.get<TDatabricksListSecretScopesResponse>(
`${removeTrailingSlash(workspaceUrl)}/api/2.0/secrets/scopes/list`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
}
);
// not present in response if no scopes exists
return data.scopes ?? [];
};
export const databricksConnectionService = (
getAppConnection: TGetAppConnectionFunc,
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
) => {
const listSecretScopes = async (connectionId: string, actor: OrgServiceActor) => {
const appConnection = await getAppConnection(AppConnection.Databricks, connectionId, actor);
const secretScopes = await listDatabricksSecretScopes(appConnection, appConnectionDAL, kmsService);
return secretScopes;
};
return {
listSecretScopes
};
};

View File

@@ -0,0 +1,36 @@
import { z } from "zod";
import { DiscriminativePick } from "@app/lib/types";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
CreateDatabricksConnectionSchema,
DatabricksConnectionSchema,
ValidateDatabricksConnectionCredentialsSchema
} from "./databricks-connection-schemas";
export type TDatabricksConnection = z.infer<typeof DatabricksConnectionSchema>;
export type TDatabricksConnectionInput = z.infer<typeof CreateDatabricksConnectionSchema> & {
app: AppConnection.Databricks;
};
export type TValidateDatabricksConnectionCredentials = typeof ValidateDatabricksConnectionCredentialsSchema;
export type TDatabricksConnectionConfig = DiscriminativePick<
TDatabricksConnection,
"method" | "app" | "credentials"
> & {
orgId: string;
};
export type TAuthorizeDatabricksConnection = {
access_token: string;
scope: string;
token_type: string;
expires_in: number;
};
export type TDatabricksListSecretScopesResponse = {
scopes?: { name: string; backend_type: string; keyvault_metadata: { resource_id: string; dns_name: string } }[];
};

View File

@@ -0,0 +1,4 @@
export * from "./databricks-connection-enums";
export * from "./databricks-connection-fns";
export * from "./databricks-connection-schemas";
export * from "./databricks-connection-types";

View File

@@ -17,7 +17,7 @@ import {
TGcpConnectionConfig
} from "./gcp-connection-types";
export const getGcpAppConnectionListItem = () => {
export const getGcpConnectionListItem = () => {
return {
name: "GCP" as const,
app: AppConnection.GCP as const,

View File

@@ -10,14 +10,14 @@ import {
} from "@app/services/secret-sync/secret-sync-schemas";
const AwsParameterStoreSyncDestinationConfigSchema = z.object({
region: z.nativeEnum(AWSRegion).describe(SecretSyncs.DESTINATION_CONFIG.AWS_PARAMETER_STORE.REGION),
region: z.nativeEnum(AWSRegion).describe(SecretSyncs.DESTINATION_CONFIG.AWS_PARAMETER_STORE.region),
path: z
.string()
.trim()
.min(1, "Parameter Store Path required")
.max(2048, "Cannot exceed 2048 characters")
.regex(/^\/([/]|(([\w-]+\/)+))?$/, 'Invalid path - must follow "/example/path/" format')
.describe(SecretSyncs.DESTINATION_CONFIG.AWS_PARAMETER_STORE.PATH)
.describe(SecretSyncs.DESTINATION_CONFIG.AWS_PARAMETER_STORE.path)
});
export const AwsParameterStoreSyncSchema = BaseSecretSyncSchema(SecretSync.AWSParameterStore).extend({

View File

@@ -15,12 +15,12 @@ const AwsSecretsManagerSyncDestinationConfigSchema = z
z.object({
mappingBehavior: z
.literal(AwsSecretsManagerSyncMappingBehavior.OneToOne)
.describe(SecretSyncs.DESTINATION_CONFIG.AWS_SECRETS_MANAGER.MAPPING_BEHAVIOR)
.describe(SecretSyncs.DESTINATION_CONFIG.AWS_SECRETS_MANAGER.mappingBehavior)
}),
z.object({
mappingBehavior: z
.literal(AwsSecretsManagerSyncMappingBehavior.ManyToOne)
.describe(SecretSyncs.DESTINATION_CONFIG.AWS_SECRETS_MANAGER.MAPPING_BEHAVIOR),
.describe(SecretSyncs.DESTINATION_CONFIG.AWS_SECRETS_MANAGER.mappingBehavior),
secretName: z
.string()
.regex(
@@ -29,12 +29,12 @@ const AwsSecretsManagerSyncDestinationConfigSchema = z
)
.min(1, "Secret name is required")
.max(256, "Secret name cannot exceed 256 characters")
.describe(SecretSyncs.DESTINATION_CONFIG.AWS_SECRETS_MANAGER.SECRET_NAME)
.describe(SecretSyncs.DESTINATION_CONFIG.AWS_SECRETS_MANAGER.secretName)
})
])
.and(
z.object({
region: z.nativeEnum(AWSRegion).describe(SecretSyncs.DESTINATION_CONFIG.AWS_SECRETS_MANAGER.REGION)
region: z.nativeEnum(AWSRegion).describe(SecretSyncs.DESTINATION_CONFIG.AWS_SECRETS_MANAGER.region)
})
);

View File

@@ -11,7 +11,7 @@ import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
import { TAzureAppConfigurationSyncWithCredentials } from "./azure-app-configuration-sync-types";
type TAzureAppConfigurationSecretSyncFactoryDeps = {
type TAzureAppConfigurationSyncFactoryDeps = {
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
};
@@ -22,10 +22,10 @@ interface AzureAppConfigKeyValue {
label?: string;
}
export const azureAppConfigurationSecretSyncFactory = ({
export const azureAppConfigurationSyncFactory = ({
kmsService,
appConnectionDAL
}: TAzureAppConfigurationSecretSyncFactoryDeps) => {
}: TAzureAppConfigurationSyncFactoryDeps) => {
const $getCompleteAzureAppConfigValues = async (accessToken: string, baseURL: string, url: string) => {
let result: AzureAppConfigKeyValue[] = [];
let currentUrl = url;

View File

@@ -14,8 +14,8 @@ const AzureAppConfigurationSyncDestinationConfigSchema = z.object({
configurationUrl: z
.string()
.min(1, "App Configuration URL required")
.describe(SecretSyncs.DESTINATION_CONFIG.AZURE_APP_CONFIGURATION.CONFIGURATION_URL),
label: z.string().optional().describe(SecretSyncs.DESTINATION_CONFIG.AZURE_APP_CONFIGURATION.LABEL)
.describe(SecretSyncs.DESTINATION_CONFIG.AZURE_APP_CONFIGURATION.configurationUrl),
label: z.string().optional().describe(SecretSyncs.DESTINATION_CONFIG.AZURE_APP_CONFIGURATION.label)
});
const AzureAppConfigurationSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };

View File

@@ -10,15 +10,12 @@ import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
import { SecretSyncError } from "../secret-sync-errors";
import { GetAzureKeyVaultSecret, TAzureKeyVaultSyncWithCredentials } from "./azure-key-vault-sync-types";
type TAzureKeyVaultSecretSyncFactoryDeps = {
type TAzureKeyVaultSyncFactoryDeps = {
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
};
export const azureKeyVaultSecretSyncFactory = ({
kmsService,
appConnectionDAL
}: TAzureKeyVaultSecretSyncFactoryDeps) => {
export const azureKeyVaultSyncFactory = ({ kmsService, appConnectionDAL }: TAzureKeyVaultSyncFactoryDeps) => {
const $getAzureKeyVaultSecrets = async (accessToken: string, vaultBaseUrl: string) => {
const paginateAzureKeyVaultSecrets = async () => {
let result: GetAzureKeyVaultSecret[] = [];

View File

@@ -15,7 +15,7 @@ const AzureKeyVaultSyncDestinationConfigSchema = z.object({
.string()
.url("Invalid vault base URL format")
.min(1, "Vault base URL required")
.describe(SecretSyncs.DESTINATION_CONFIG.AZURE_KEY_VAULT.VAULT_BASE_URL)
.describe(SecretSyncs.DESTINATION_CONFIG.AZURE_KEY_VAULT.vaultBaseUrl)
});
const AzureKeyVaultSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };

View 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 DATABRICKS_SYNC_LIST_OPTION: TSecretSyncListItem = {
name: "Databricks",
destination: SecretSync.Databricks,
connection: AppConnection.Databricks,
canImportSecrets: false
};

View File

@@ -0,0 +1,164 @@
import { request } from "@app/lib/config/request";
import { removeTrailingSlash } from "@app/lib/fn";
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
import { getDatabricksConnectionAccessToken } from "@app/services/app-connection/databricks";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import {
TDatabricksDeleteSecret,
TDatabricksListSecretKeys,
TDatabricksListSecretKeysResponse,
TDatabricksPutSecret,
TDatabricksSyncWithCredentials
} from "@app/services/secret-sync/databricks/databricks-sync-types";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
import { SECRET_SYNC_NAME_MAP } from "@app/services/secret-sync/secret-sync-maps";
import { TSecretMap } from "../secret-sync-types";
type TDatabricksSecretSyncFactoryDeps = {
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
};
const DATABRICKS_SCOPE_SECRET_LIMIT = 1000;
const listDatabricksSecrets = async ({ workspaceUrl, scope, accessToken }: TDatabricksListSecretKeys) => {
const { data } = await request.get<TDatabricksListSecretKeysResponse>(
`${removeTrailingSlash(workspaceUrl)}/api/2.0/secrets/list`,
{
params: {
scope
},
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
}
);
// not present in response if no secrets exist in scope
return data.secrets ?? [];
};
const putDatabricksSecret = async ({ workspaceUrl, scope, key, value, accessToken }: TDatabricksPutSecret) =>
request.post(
`${removeTrailingSlash(workspaceUrl)}/api/2.0/secrets/put`,
{
scope,
key,
string_value: value
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
}
);
const deleteDatabricksSecrets = async ({ workspaceUrl, scope, key, accessToken }: TDatabricksDeleteSecret) =>
request.post(
`${removeTrailingSlash(workspaceUrl)}/api/2.0/secrets/delete`,
{
scope,
key
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
}
);
export const databricksSyncFactory = ({ kmsService, appConnectionDAL }: TDatabricksSecretSyncFactoryDeps) => {
const syncSecrets = async (secretSync: TDatabricksSyncWithCredentials, secretMap: TSecretMap) => {
if (Object.keys(secretSync).length > DATABRICKS_SCOPE_SECRET_LIMIT) {
throw new Error(
`Databricks does not support storing more than ${DATABRICKS_SCOPE_SECRET_LIMIT} secrets per scope.`
);
}
const {
destinationConfig: { scope },
connection
} = secretSync;
const { workspaceUrl } = connection.credentials;
const accessToken = await getDatabricksConnectionAccessToken(connection, appConnectionDAL, kmsService);
for await (const entry of Object.entries(secretMap)) {
const [key, { value }] = entry;
try {
await putDatabricksSecret({
key,
value,
workspaceUrl,
scope,
accessToken
});
} catch (error) {
throw new SecretSyncError({
error,
secretKey: key
});
}
}
const databricksSecretKeys = await listDatabricksSecrets({
workspaceUrl,
scope,
accessToken
});
for await (const secret of databricksSecretKeys) {
if (!(secret.key in secretMap)) {
await deleteDatabricksSecrets({
key: secret.key,
workspaceUrl,
scope,
accessToken
});
}
}
};
const removeSecrets = async (secretSync: TDatabricksSyncWithCredentials, secretMap: TSecretMap) => {
const {
destinationConfig: { scope },
connection
} = secretSync;
const { workspaceUrl } = connection.credentials;
const accessToken = await getDatabricksConnectionAccessToken(connection, appConnectionDAL, kmsService);
const databricksSecretKeys = await listDatabricksSecrets({
workspaceUrl,
scope,
accessToken
});
for await (const secret of databricksSecretKeys) {
if (secret.key in secretMap) {
await deleteDatabricksSecrets({
key: secret.key,
workspaceUrl,
scope,
accessToken
});
}
}
};
const getSecrets = async (secretSync: TDatabricksSyncWithCredentials) => {
throw new Error(`${SECRET_SYNC_NAME_MAP[secretSync.destination]} does not support importing secrets.`);
};
return {
syncSecrets,
removeSecrets,
getSecrets
};
};

View File

@@ -0,0 +1,43 @@
import { z } from "zod";
import { SecretSyncs } from "@app/lib/api-docs";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import {
BaseSecretSyncSchema,
GenericCreateSecretSyncFieldsSchema,
GenericUpdateSecretSyncFieldsSchema
} from "@app/services/secret-sync/secret-sync-schemas";
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
const DatabricksSyncDestinationConfigSchema = z.object({
scope: z.string().trim().min(1, "Databricks scope required").describe(SecretSyncs.DESTINATION_CONFIG.DATABRICKS.scope)
});
const DatabricksSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false };
export const DatabricksSyncSchema = BaseSecretSyncSchema(SecretSync.Databricks, DatabricksSyncOptionsConfig).extend({
destination: z.literal(SecretSync.Databricks),
destinationConfig: DatabricksSyncDestinationConfigSchema
});
export const CreateDatabricksSyncSchema = GenericCreateSecretSyncFieldsSchema(
SecretSync.Databricks,
DatabricksSyncOptionsConfig
).extend({
destinationConfig: DatabricksSyncDestinationConfigSchema
});
export const UpdateDatabricksSyncSchema = GenericUpdateSecretSyncFieldsSchema(
SecretSync.Databricks,
DatabricksSyncOptionsConfig
).extend({
destinationConfig: DatabricksSyncDestinationConfigSchema.optional()
});
export const DatabricksSyncListItemSchema = z.object({
name: z.literal("Databricks"),
connection: z.literal(AppConnection.Databricks),
destination: z.literal(SecretSync.Databricks),
canImportSecrets: z.literal(false)
});

View File

@@ -0,0 +1,40 @@
import { z } from "zod";
import { TDatabricksConnection } from "@app/services/app-connection/databricks";
import {
CreateDatabricksSyncSchema,
DatabricksSyncListItemSchema,
DatabricksSyncSchema
} from "./databricks-sync-schemas";
export type TDatabricksSync = z.infer<typeof DatabricksSyncSchema>;
export type TDatabricksSyncInput = z.infer<typeof CreateDatabricksSyncSchema>;
export type TDatabricksSyncListItem = z.infer<typeof DatabricksSyncListItemSchema>;
export type TDatabricksSyncWithCredentials = TDatabricksSync & {
connection: TDatabricksConnection;
};
export type TDatabricksListSecretKeysResponse = {
secrets?: { key: string; last_updated_timestamp: number }[];
};
type TBaseDatabricksSecretRequest = {
scope: string;
workspaceUrl: string;
accessToken: string;
};
export type TDatabricksListSecretKeys = TBaseDatabricksSecretRequest;
export type TDatabricksPutSecret = {
key: string;
value?: string;
} & TBaseDatabricksSecretRequest;
export type TDatabricksDeleteSecret = {
key: string;
} & TBaseDatabricksSecretRequest;

View File

@@ -0,0 +1,4 @@
export * from "./databricks-sync-constants";
export * from "./databricks-sync-fns";
export * from "./databricks-sync-schemas";
export * from "./databricks-sync-types";

View File

@@ -1,5 +1,6 @@
import z from "zod";
import { SecretSyncs } from "@app/lib/api-docs";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
BaseSecretSyncSchema,
@@ -14,8 +15,8 @@ import { GcpSyncScope } from "./gcp-sync-enums";
const GcpSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };
const GcpSyncDestinationConfigSchema = z.object({
scope: z.literal(GcpSyncScope.Global),
projectId: z.string().min(1, "Project ID is required")
scope: z.literal(GcpSyncScope.Global).describe(SecretSyncs.DESTINATION_CONFIG.GCP.scope),
projectId: z.string().min(1, "Project ID is required").describe(SecretSyncs.DESTINATION_CONFIG.GCP.projectId)
});
export const GcpSyncSchema = BaseSecretSyncSchema(SecretSync.GCPSecretManager, GcpSyncOptionsConfig).extend({

View File

@@ -14,21 +14,21 @@ import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"
const GitHubSyncDestinationConfigSchema = z
.discriminatedUnion("scope", [
z.object({
scope: z.literal(GitHubSyncScope.Organization),
org: z.string().min(1, "Organization name required").describe(SecretSyncs.DESTINATION_CONFIG.GITHUB.ORG),
scope: z.literal(GitHubSyncScope.Organization).describe(SecretSyncs.DESTINATION_CONFIG.GITHUB.scope),
org: z.string().min(1, "Organization name required").describe(SecretSyncs.DESTINATION_CONFIG.GITHUB.org),
visibility: z.nativeEnum(GitHubSyncVisibility),
selectedRepositoryIds: z.number().array().optional()
}),
z.object({
scope: z.literal(GitHubSyncScope.Repository),
owner: z.string().min(1, "Repository owner name required").describe(SecretSyncs.DESTINATION_CONFIG.GITHUB.OWNER),
repo: z.string().min(1, "Repository name required").describe(SecretSyncs.DESTINATION_CONFIG.GITHUB.REPO)
scope: z.literal(GitHubSyncScope.Repository).describe(SecretSyncs.DESTINATION_CONFIG.GITHUB.scope),
owner: z.string().min(1, "Repository owner name required").describe(SecretSyncs.DESTINATION_CONFIG.GITHUB.owner),
repo: z.string().min(1, "Repository name required").describe(SecretSyncs.DESTINATION_CONFIG.GITHUB.repo)
}),
z.object({
scope: z.literal(GitHubSyncScope.RepositoryEnvironment),
owner: z.string().min(1, "Repository owner name required").describe(SecretSyncs.DESTINATION_CONFIG.GITHUB.OWNER),
repo: z.string().min(1, "Repository name required").describe(SecretSyncs.DESTINATION_CONFIG.GITHUB.REPO),
env: z.string().min(1, "Environment name required").describe(SecretSyncs.DESTINATION_CONFIG.GITHUB.ENV)
scope: z.literal(GitHubSyncScope.RepositoryEnvironment).describe(SecretSyncs.DESTINATION_CONFIG.GITHUB.scope),
owner: z.string().min(1, "Repository owner name required").describe(SecretSyncs.DESTINATION_CONFIG.GITHUB.owner),
repo: z.string().min(1, "Repository name required").describe(SecretSyncs.DESTINATION_CONFIG.GITHUB.repo),
env: z.string().min(1, "Environment name required").describe(SecretSyncs.DESTINATION_CONFIG.GITHUB.env)
})
])
.superRefine((options, ctx) => {

View File

@@ -4,7 +4,8 @@ export enum SecretSync {
GitHub = "github",
GCPSecretManager = "gcp-secret-manager",
AzureKeyVault = "azure-key-vault",
AzureAppConfiguration = "azure-app-configuration"
AzureAppConfiguration = "azure-app-configuration",
Databricks = "databricks"
}
export enum SecretSyncInitialSyncBehavior {

View File

@@ -8,6 +8,7 @@ import {
AWS_SECRETS_MANAGER_SYNC_LIST_OPTION,
AwsSecretsManagerSyncFns
} from "@app/services/secret-sync/aws-secrets-manager";
import { DATABRICKS_SYNC_LIST_OPTION, databricksSyncFactory } from "@app/services/secret-sync/databricks";
import { GITHUB_SYNC_LIST_OPTION, GithubSyncFns } from "@app/services/secret-sync/github";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
@@ -19,11 +20,8 @@ import {
import { TAppConnectionDALFactory } from "../app-connection/app-connection-dal";
import { TKmsServiceFactory } from "../kms/kms-service";
import {
AZURE_APP_CONFIGURATION_SYNC_LIST_OPTION,
azureAppConfigurationSecretSyncFactory
} from "./azure-app-configuration";
import { AZURE_KEY_VAULT_SYNC_LIST_OPTION, azureKeyVaultSecretSyncFactory } from "./azure-key-vault";
import { AZURE_APP_CONFIGURATION_SYNC_LIST_OPTION, azureAppConfigurationSyncFactory } from "./azure-app-configuration";
import { AZURE_KEY_VAULT_SYNC_LIST_OPTION, azureKeyVaultSyncFactory } from "./azure-key-vault";
import { GCP_SYNC_LIST_OPTION } from "./gcp";
import { GcpSyncFns } from "./gcp/gcp-sync-fns";
@@ -33,7 +31,8 @@ const SECRET_SYNC_LIST_OPTIONS: Record<SecretSync, TSecretSyncListItem> = {
[SecretSync.GitHub]: GITHUB_SYNC_LIST_OPTION,
[SecretSync.GCPSecretManager]: GCP_SYNC_LIST_OPTION,
[SecretSync.AzureKeyVault]: AZURE_KEY_VAULT_SYNC_LIST_OPTION,
[SecretSync.AzureAppConfiguration]: AZURE_APP_CONFIGURATION_SYNC_LIST_OPTION
[SecretSync.AzureAppConfiguration]: AZURE_APP_CONFIGURATION_SYNC_LIST_OPTION,
[SecretSync.Databricks]: DATABRICKS_SYNC_LIST_OPTION
};
export const listSecretSyncOptions = () => {
@@ -41,7 +40,7 @@ export const listSecretSyncOptions = () => {
};
type TSyncSecretDeps = {
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update">;
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update" | "updateById">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
};
@@ -103,12 +102,17 @@ export const SecretSyncFns = {
case SecretSync.GCPSecretManager:
return GcpSyncFns.syncSecrets(secretSync, secretMap);
case SecretSync.AzureKeyVault:
return azureKeyVaultSecretSyncFactory({
return azureKeyVaultSyncFactory({
appConnectionDAL,
kmsService
}).syncSecrets(secretSync, secretMap);
case SecretSync.AzureAppConfiguration:
return azureAppConfigurationSecretSyncFactory({
return azureAppConfigurationSyncFactory({
appConnectionDAL,
kmsService
}).syncSecrets(secretSync, secretMap);
case SecretSync.Databricks:
return databricksSyncFactory({
appConnectionDAL,
kmsService
}).syncSecrets(secretSync, secretMap);
@@ -137,17 +141,22 @@ export const SecretSyncFns = {
secretMap = await GcpSyncFns.getSecrets(secretSync);
break;
case SecretSync.AzureKeyVault:
secretMap = await azureKeyVaultSecretSyncFactory({
secretMap = await azureKeyVaultSyncFactory({
appConnectionDAL,
kmsService
}).getSecrets(secretSync);
break;
case SecretSync.AzureAppConfiguration:
secretMap = await azureAppConfigurationSecretSyncFactory({
secretMap = await azureAppConfigurationSyncFactory({
appConnectionDAL,
kmsService
}).getSecrets(secretSync);
break;
case SecretSync.Databricks:
return databricksSyncFactory({
appConnectionDAL,
kmsService
}).getSecrets(secretSync);
default:
throw new Error(
`Unhandled sync destination for get secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
@@ -174,12 +183,17 @@ export const SecretSyncFns = {
case SecretSync.GCPSecretManager:
return GcpSyncFns.removeSecrets(secretSync, secretMap);
case SecretSync.AzureKeyVault:
return azureKeyVaultSecretSyncFactory({
return azureKeyVaultSyncFactory({
appConnectionDAL,
kmsService
}).removeSecrets(secretSync, secretMap);
case SecretSync.AzureAppConfiguration:
return azureAppConfigurationSecretSyncFactory({
return azureAppConfigurationSyncFactory({
appConnectionDAL,
kmsService
}).removeSecrets(secretSync, secretMap);
case SecretSync.Databricks:
return databricksSyncFactory({
appConnectionDAL,
kmsService
}).removeSecrets(secretSync, secretMap);

View File

@@ -7,7 +7,8 @@ export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
[SecretSync.GitHub]: "GitHub",
[SecretSync.GCPSecretManager]: "GCP Secret Manager",
[SecretSync.AzureKeyVault]: "Azure Key Vault",
[SecretSync.AzureAppConfiguration]: "Azure App Configuration"
[SecretSync.AzureAppConfiguration]: "Azure App Configuration",
[SecretSync.Databricks]: "Databricks"
};
export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
@@ -16,5 +17,6 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
[SecretSync.GitHub]: AppConnection.GitHub,
[SecretSync.GCPSecretManager]: AppConnection.GCP,
[SecretSync.AzureKeyVault]: AppConnection.AzureKeyVault,
[SecretSync.AzureAppConfiguration]: AppConnection.AzureAppConfiguration
[SecretSync.AzureAppConfiguration]: AppConnection.AzureAppConfiguration,
[SecretSync.Databricks]: AppConnection.Databricks
};

View File

@@ -64,7 +64,7 @@ export type TSecretSyncQueueFactory = ReturnType<typeof secretSyncQueueFactory>;
type TSecretSyncQueueFactoryDep = {
queueService: Pick<TQueueServiceFactory, "queue" | "start">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update">;
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "update" | "updateById">;
keyStore: Pick<TKeyStoreFactory, "acquireLock" | "setItemWithExpiry" | "getItem">;
folderDAL: TSecretFolderDALFactory;
secretV2BridgeDAL: Pick<

View File

@@ -13,7 +13,7 @@ const SyncOptionsSchema = (secretSync: SecretSync, options: TSyncOptionsConfig =
initialSyncBehavior: (options.canImportSecrets
? z.nativeEnum(SecretSyncInitialSyncBehavior)
: z.literal(SecretSyncInitialSyncBehavior.OverwriteDestination)
).describe(SecretSyncs.SYNC_OPTIONS(secretSync).INITIAL_SYNC_BEHAVIOR)
).describe(SecretSyncs.SYNC_OPTIONS(secretSync).initialSyncBehavior)
// prependPrefix: z
// .string()
// .trim()

View File

@@ -8,6 +8,12 @@ import {
TAwsSecretsManagerSyncListItem,
TAwsSecretsManagerSyncWithCredentials
} from "@app/services/secret-sync/aws-secrets-manager";
import {
TDatabricksSync,
TDatabricksSyncInput,
TDatabricksSyncListItem,
TDatabricksSyncWithCredentials
} from "@app/services/secret-sync/databricks";
import {
TGitHubSync,
TGitHubSyncInput,
@@ -43,7 +49,8 @@ export type TSecretSync =
| TGitHubSync
| TGcpSync
| TAzureKeyVaultSync
| TAzureAppConfigurationSync;
| TAzureAppConfigurationSync
| TDatabricksSync;
export type TSecretSyncWithCredentials =
| TAwsParameterStoreSyncWithCredentials
@@ -51,7 +58,8 @@ export type TSecretSyncWithCredentials =
| TGitHubSyncWithCredentials
| TGcpSyncWithCredentials
| TAzureKeyVaultSyncWithCredentials
| TAzureAppConfigurationSyncWithCredentials;
| TAzureAppConfigurationSyncWithCredentials
| TDatabricksSyncWithCredentials;
export type TSecretSyncInput =
| TAwsParameterStoreSyncInput
@@ -59,7 +67,8 @@ export type TSecretSyncInput =
| TGitHubSyncInput
| TGcpSyncInput
| TAzureKeyVaultSyncInput
| TAzureAppConfigurationSyncInput;
| TAzureAppConfigurationSyncInput
| TDatabricksSyncInput;
export type TSecretSyncListItem =
| TAwsParameterStoreSyncListItem
@@ -67,7 +76,8 @@ export type TSecretSyncListItem =
| TGitHubSyncListItem
| TGcpSyncListItem
| TAzureKeyVaultSyncListItem
| TAzureAppConfigurationSyncListItem;
| TAzureAppConfigurationSyncListItem
| TDatabricksSyncListItem;
export type TSyncOptionsConfig = {
canImportSecrets: boolean;

View File

@@ -0,0 +1,4 @@
---
title: "Available"
openapi: "GET /api/v1/app-connections/databricks/available"
---

View File

@@ -0,0 +1,4 @@
---
title: "Create"
openapi: "POST /api/v1/app-connections/databricks"
---

View File

@@ -0,0 +1,4 @@
---
title: "Delete"
openapi: "DELETE /api/v1/app-connections/databricks/{connectionId}"
---

View File

@@ -0,0 +1,4 @@
---
title: "Get by ID"
openapi: "GET /api/v1/app-connections/databricks/{connectionId}"
---

View File

@@ -0,0 +1,4 @@
---
title: "Get by Name"
openapi: "GET /api/v1/app-connections/databricks/connection-name/{connectionName}"
---

View File

@@ -0,0 +1,4 @@
---
title: "List"
openapi: "GET /api/v1/app-connections/databricks"
---

View File

@@ -0,0 +1,4 @@
---
title: "Update"
openapi: "PATCH /api/v1/app-connections/databricks/{connectionId}"
---

View File

@@ -0,0 +1,4 @@
---
title: "Create"
openapi: "POST /api/v1/secret-syncs/databricks"
---

View File

@@ -0,0 +1,4 @@
---
title: "Delete"
openapi: "DELETE /api/v1/secret-syncs/databricks/{syncId}"
---

View File

@@ -0,0 +1,4 @@
---
title: "Get by ID"
openapi: "GET /api/v1/secret-syncs/databricks/{syncId}"
---

View File

@@ -0,0 +1,4 @@
---
title: "Get by Name"
openapi: "GET /api/v1/secret-syncs/databricks/sync-name/{syncName}"
---

View File

@@ -0,0 +1,4 @@
---
title: "List"
openapi: "GET /api/v1/secret-syncs/databricks"
---

View File

@@ -0,0 +1,4 @@
---
title: "Remove Secrets"
openapi: "POST /api/v1/secret-syncs/databricks/{syncId}/remove-secrets"
---

View File

@@ -0,0 +1,4 @@
---
title: "Sync Secrets"
openapi: "POST /api/v1/secret-syncs/databricks/{syncId}/sync-secrets"
---

View File

@@ -0,0 +1,4 @@
---
title: "Update"
openapi: "PATCH /api/v1/secret-syncs/databricks/{syncId}"
---

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 743 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 512 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 808 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 853 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 827 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 804 KiB

View File

@@ -62,7 +62,7 @@ Infisical currently only supports one method for connecting to Azure, which is O
## Setup Azure Connection in Infisical
<Steps>
<Step title="Navigate to the App Connections">
<Step title="Navigate to App Connections">
Navigate to the **App Connections** tab on the **Organization Settings** page. ![App Connections
Tab](/images/app-connections/general/add-connection.png)
</Step>

View File

@@ -61,7 +61,7 @@ Infisical currently only supports one method for connecting to Azure, which is O
## Setup Azure Connection in Infisical
<Steps>
<Step title="Navigate to the App Connections">
<Step title="Navigate to App Connections">
Navigate to the **App Connections** tab on the **Organization Settings** page. ![App Connections
Tab](/images/app-connections/general/add-connection.png)
</Step>

View File

@@ -0,0 +1,64 @@
---
title: "Databricks Connection"
description: "Learn how to configure a Databricks Connection for Infisical."
---
Infisical supports the use of [service principals](https://docs.databricks.com/en/admin/users-groups/service-principals.html) to connect with your Databricks workspaces.
## Configure a Service Principal for Infisical
<Steps>
<Step title="Databricks Workspace Settings">
Navigate to your Databricks Workspace **Settings** via the dropdown in the top right.
![Workspace Settings Page](/images/app-connections/databricks/workspace-settings.png)
</Step>
<Step title="Manage Service Principals">
Under the **Identity & Access** tab, click the **Manage** button in the **Service Principals** section.
![Manage Service Principals](/images/app-connections/databricks/manage-service-principals.png)
</Step>
<Step title="Service Principal Management">
Click the **Add Service Principal** button.
![Add Service Principal](/images/app-connections/databricks/add-service-principal.png)
</Step>
<Step title="Add Service Principal">
Select the **Add New** option and create a service principal for Infisical.
![Create Service Principal](/images/app-connections/databricks/create-service-principal.png)
</Step>
<Step title="Generate Service Principal Secret">
Click on your new service principal, select the **Secrets** tab and click the **Generate Secret** button.
![Generate Secret](/images/app-connections/databricks/service-principal-secrets.png)
</Step>
<Step title="Service Principal Secret">
Copy your service principal **Secret** and **Client ID** for use in the following steps.
![Generate Secret](/images/app-connections/databricks/service-principal-ids.png)
</Step>
</Steps>
## Setup Databricks Connection in Infisical
<Steps>
<Step title="Navigate to App Connections">
Navigate to the **App Connections** tab on the **Organization Settings**
page. ![App Connections
Tab](/images/app-connections/general/add-connection.png)
</Step>
<Step title="Add Connection">
Select the **Databricks Connection** option from the connection options modal.
![Select Databricks
Connection](/images/app-connections/databricks/select-databricks-connection.png)
</Step>
<Step title="Authorize Connection">
Select the **Service Principal** method, add your **workspace URL** and **service principal credentials**, then click **Connect to
Databricks**. ![Connect via Databricks
service principal](/images/app-connections/databricks/create-databricks-service-principal-method.png)
</Step>
<Step title="Connection Created">
Your **Databricks Connection** is now available for use. ![Databricks Service Principal
Connection](/images/app-connections/databricks/databricks-service-principal-connection.png)
</Step>
</Steps>

View File

@@ -81,7 +81,7 @@ Infisical supports [service account impersonation](https://cloud.google.com/iam/
## Setup GCP Connection in Infisical
<Steps>
<Step title="Navigate to the App Connections">
<Step title="Navigate to App Connections">
Navigate to the **App Connections** tab on the **Organization Settings**
page. ![App Connections
Tab](/images/app-connections/general/add-connection.png)

View File

@@ -69,7 +69,7 @@ Infisical supports two methods for connecting to GitHub.
## Setup GitHub Connection in Infisical
<Steps>
<Step title="Navigate to the App Connections">
<Step title="Navigate to App Connections">
Navigate to the **App Connections** tab on the **Organization Settings** page.
![App Connections Tab](/images/app-connections/general/add-connection.png)
</Step>
@@ -135,7 +135,7 @@ Infisical supports two methods for connecting to GitHub.
## Setup GitHub Connection in Infisical
<Steps>
<Step title="Navigate to the App Connections">
<Step title="Navigate to App Connections">
Navigate to the **App Connections** tab on the **Organization Settings** page.
![App Connections Tab](/images/app-connections/general/add-connection.png)
</Step>

View File

@@ -0,0 +1,140 @@
---
title: "Databricks Sync"
description: "Learn how to configure a Databricks Sync for Infisical."
---
**Prerequisites:**
- Set up and add secrets to [Infisical Cloud](https://app.infisical.com)
- Create a [Databricks Connection](/integrations/app-connections/databricks)
<Tabs>
<Tab title="Infisical UI">
1. Navigate to **Project** > **Integrations** and select the **Secret Syncs** tab. Click on the **Add Sync** button.
![Secret Syncs Tab](/images/secret-syncs/general/secret-sync-tab.png)
2. Select the **Databricks** option.
![Select Databricks](/images/secret-syncs/databricks/select-databricks-option.png)
3. Configure the **Source** from where secrets should be retrieved, then click **Next**.
![Configure Source](/images/secret-syncs/databricks/databricks-source.png)
- **Environment**: The project environment to retrieve secrets from.
- **Secret Path**: The folder path to retrieve secrets from.
<Tip>
If you need to sync secrets from multiple folder locations, check out [secret imports](/documentation/platform/secret-reference#secret-imports).
</Tip>
4. Configure the **Destination** to where secrets should be deployed, then click **Next**.
![Configure Destination](/images/secret-syncs/databricks/databricks-destination.png)
- **Databricks Connection**: The Databricks Connection to authenticate with.
- **Scope**: The Databricks secret scope to sync secrets to.
<Note>
You must create a secret scope in your Databricks workspace prior to configuration. Ensure your service principal has [Write permissions](https://docs.databricks.com/en/security/auth/access-control/index.html#secret-acls) for the specified secret scope.
</Note>
5. Configure the **Sync Options** to specify how secrets should be synced, then click **Next**.
![Configure Options](/images/secret-syncs/databricks/databricks-options.png)
- **Initial Sync Behavior**: Determines how Infisical should resolve the initial sync.
- **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical.
<Note>
Databricks does not support importing secrets.
</Note>
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
6. Configure the **Details** of your Databricks Sync, then click **Next**.
![Configure Details](/images/secret-syncs/databricks/databricks-details.png)
- **Name**: The name of your sync. Must be slug-friendly.
- **Description**: An optional description for your sync.
7. Review your Databricks Sync configuration, then click **Create Sync**.
![Confirm Configuration](/images/secret-syncs/databricks/databricks-review.png)
8. If enabled, your Databricks Sync will begin syncing your secrets to the destination endpoint.
![Sync Secrets](/images/secret-syncs/databricks/databricks-created.png)
</Tab>
<Tab title="API">
To create an **Databricks Sync**, make an API request to the [Create Databricks Sync](/api-reference/endpoints/secret-syncs/databricks/create) API endpoint.
### Sample request
```bash Request
curl --request POST \
--url https://app.infisical.com/api/v1/secret-syncs/databricks \
--header 'Content-Type: application/json' \
--data '{
"name": "my-databricks-sync",
"projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"description": "an example sync",
"connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"environment": "dev",
"secretPath": "/my-secrets",
"isEnabled": true,
"syncOptions": {
"initialSyncBehavior": "overwrite-destination"
},
"destinationConfig": {
"scope": "my-scope"
}
}'
```
### Sample response
```bash Response
{
"secretSync": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "my-databricks-sync",
"description": "an example sync",
"isEnabled": true,
"version": 1,
"folderId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"connectionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"createdAt": "2023-11-07T05:31:56Z",
"updatedAt": "2023-11-07T05:31:56Z",
"syncStatus": "succeeded",
"lastSyncJobId": "123",
"lastSyncMessage": null,
"lastSyncedAt": "2023-11-07T05:31:56Z",
"importStatus": null,
"lastImportJobId": null,
"lastImportMessage": null,
"lastImportedAt": null,
"removeStatus": null,
"lastRemoveJobId": null,
"lastRemoveMessage": null,
"lastRemovedAt": null,
"syncOptions": {
"initialSyncBehavior": "overwrite-destination"
},
"projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"connection": {
"app": "databricks",
"name": "my-databricks-connection",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
},
"environment": {
"slug": "dev",
"name": "Development",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
},
"folder": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"path": "/my-secrets"
},
"destination": "databricks",
"destinationConfig": {
"scope": "my-scope"
}
}
}
```
</Tab>
</Tabs>

View File

@@ -392,10 +392,11 @@
"group": "Connections",
"pages": [
"integrations/app-connections/aws",
"integrations/app-connections/github",
"integrations/app-connections/gcp",
"integrations/app-connections/azure-app-configuration",
"integrations/app-connections/azure-key-vault",
"integrations/app-connections/azure-app-configuration"
"integrations/app-connections/databricks",
"integrations/app-connections/gcp",
"integrations/app-connections/github"
]
}
]
@@ -409,10 +410,11 @@
"pages": [
"integrations/secret-syncs/aws-parameter-store",
"integrations/secret-syncs/aws-secrets-manager",
"integrations/secret-syncs/github",
"integrations/secret-syncs/gcp-secret-manager",
"integrations/secret-syncs/azure-app-configuration",
"integrations/secret-syncs/azure-key-vault",
"integrations/secret-syncs/azure-app-configuration"
"integrations/secret-syncs/databricks",
"integrations/secret-syncs/gcp-secret-manager",
"integrations/secret-syncs/github"
]
}
]
@@ -825,27 +827,15 @@
]
},
{
"group": "GitHub",
"group": "Azure App Configuration",
"pages": [
"api-reference/endpoints/app-connections/github/list",
"api-reference/endpoints/app-connections/github/available",
"api-reference/endpoints/app-connections/github/get-by-id",
"api-reference/endpoints/app-connections/github/get-by-name",
"api-reference/endpoints/app-connections/github/create",
"api-reference/endpoints/app-connections/github/update",
"api-reference/endpoints/app-connections/github/delete"
]
},
{
"group": "GCP",
"pages": [
"api-reference/endpoints/app-connections/gcp/list",
"api-reference/endpoints/app-connections/gcp/available",
"api-reference/endpoints/app-connections/gcp/get-by-id",
"api-reference/endpoints/app-connections/gcp/get-by-name",
"api-reference/endpoints/app-connections/gcp/create",
"api-reference/endpoints/app-connections/gcp/update",
"api-reference/endpoints/app-connections/gcp/delete"
"api-reference/endpoints/app-connections/azure-app-configuration/list",
"api-reference/endpoints/app-connections/azure-app-configuration/available",
"api-reference/endpoints/app-connections/azure-app-configuration/get-by-id",
"api-reference/endpoints/app-connections/azure-app-configuration/get-by-name",
"api-reference/endpoints/app-connections/azure-app-configuration/create",
"api-reference/endpoints/app-connections/azure-app-configuration/update",
"api-reference/endpoints/app-connections/azure-app-configuration/delete"
]
},
{
@@ -861,15 +851,39 @@
]
},
{
"group": "Azure App Configuration",
"group": "Databricks",
"pages": [
"api-reference/endpoints/app-connections/azure-app-configuration/list",
"api-reference/endpoints/app-connections/azure-app-configuration/available",
"api-reference/endpoints/app-connections/azure-app-configuration/get-by-id",
"api-reference/endpoints/app-connections/azure-app-configuration/get-by-name",
"api-reference/endpoints/app-connections/azure-app-configuration/create",
"api-reference/endpoints/app-connections/azure-app-configuration/update",
"api-reference/endpoints/app-connections/azure-app-configuration/delete"
"api-reference/endpoints/app-connections/databricks/list",
"api-reference/endpoints/app-connections/databricks/available",
"api-reference/endpoints/app-connections/databricks/get-by-id",
"api-reference/endpoints/app-connections/databricks/get-by-name",
"api-reference/endpoints/app-connections/databricks/create",
"api-reference/endpoints/app-connections/databricks/update",
"api-reference/endpoints/app-connections/databricks/delete"
]
},
{
"group": "GCP",
"pages": [
"api-reference/endpoints/app-connections/gcp/list",
"api-reference/endpoints/app-connections/gcp/available",
"api-reference/endpoints/app-connections/gcp/get-by-id",
"api-reference/endpoints/app-connections/gcp/get-by-name",
"api-reference/endpoints/app-connections/gcp/create",
"api-reference/endpoints/app-connections/gcp/update",
"api-reference/endpoints/app-connections/gcp/delete"
]
},
{
"group": "GitHub",
"pages": [
"api-reference/endpoints/app-connections/github/list",
"api-reference/endpoints/app-connections/github/available",
"api-reference/endpoints/app-connections/github/get-by-id",
"api-reference/endpoints/app-connections/github/get-by-name",
"api-reference/endpoints/app-connections/github/create",
"api-reference/endpoints/app-connections/github/update",
"api-reference/endpoints/app-connections/github/delete"
]
}
]
@@ -908,30 +922,17 @@
]
},
{
"group": "GitHub",
"group": "Azure App Configuration",
"pages": [
"api-reference/endpoints/secret-syncs/github/list",
"api-reference/endpoints/secret-syncs/github/get-by-id",
"api-reference/endpoints/secret-syncs/github/get-by-name",
"api-reference/endpoints/secret-syncs/github/create",
"api-reference/endpoints/secret-syncs/github/update",
"api-reference/endpoints/secret-syncs/github/delete",
"api-reference/endpoints/secret-syncs/github/sync-secrets",
"api-reference/endpoints/secret-syncs/github/remove-secrets"
]
},
{
"group": "GCP Secret Manager",
"pages": [
"api-reference/endpoints/secret-syncs/gcp-secret-manager/list",
"api-reference/endpoints/secret-syncs/gcp-secret-manager/get-by-id",
"api-reference/endpoints/secret-syncs/gcp-secret-manager/get-by-name",
"api-reference/endpoints/secret-syncs/gcp-secret-manager/create",
"api-reference/endpoints/secret-syncs/gcp-secret-manager/update",
"api-reference/endpoints/secret-syncs/gcp-secret-manager/delete",
"api-reference/endpoints/secret-syncs/gcp-secret-manager/sync-secrets",
"api-reference/endpoints/secret-syncs/gcp-secret-manager/import-secrets",
"api-reference/endpoints/secret-syncs/gcp-secret-manager/remove-secrets"
"api-reference/endpoints/secret-syncs/azure-app-configuration/list",
"api-reference/endpoints/secret-syncs/azure-app-configuration/get-by-id",
"api-reference/endpoints/secret-syncs/azure-app-configuration/get-by-name",
"api-reference/endpoints/secret-syncs/azure-app-configuration/create",
"api-reference/endpoints/secret-syncs/azure-app-configuration/update",
"api-reference/endpoints/secret-syncs/azure-app-configuration/delete",
"api-reference/endpoints/secret-syncs/azure-app-configuration/sync-secrets",
"api-reference/endpoints/secret-syncs/azure-app-configuration/import-secrets",
"api-reference/endpoints/secret-syncs/azure-app-configuration/remove-secrets"
]
},
{
@@ -949,20 +950,45 @@
]
},
{
"group": "Azure App Configuration",
"group": "Databricks",
"pages": [
"api-reference/endpoints/secret-syncs/azure-app-configuration/list",
"api-reference/endpoints/secret-syncs/azure-app-configuration/get-by-id",
"api-reference/endpoints/secret-syncs/azure-app-configuration/get-by-name",
"api-reference/endpoints/secret-syncs/azure-app-configuration/create",
"api-reference/endpoints/secret-syncs/azure-app-configuration/update",
"api-reference/endpoints/secret-syncs/azure-app-configuration/delete",
"api-reference/endpoints/secret-syncs/azure-app-configuration/sync-secrets",
"api-reference/endpoints/secret-syncs/azure-app-configuration/import-secrets",
"api-reference/endpoints/secret-syncs/azure-app-configuration/remove-secrets"
"api-reference/endpoints/secret-syncs/databricks/list",
"api-reference/endpoints/secret-syncs/databricks/get-by-id",
"api-reference/endpoints/secret-syncs/databricks/get-by-name",
"api-reference/endpoints/secret-syncs/databricks/create",
"api-reference/endpoints/secret-syncs/databricks/update",
"api-reference/endpoints/secret-syncs/databricks/delete",
"api-reference/endpoints/secret-syncs/databricks/sync-secrets",
"api-reference/endpoints/secret-syncs/databricks/remove-secrets"
]
},
{
"group": "GCP Secret Manager",
"pages": [
"api-reference/endpoints/secret-syncs/gcp-secret-manager/list",
"api-reference/endpoints/secret-syncs/gcp-secret-manager/get-by-id",
"api-reference/endpoints/secret-syncs/gcp-secret-manager/get-by-name",
"api-reference/endpoints/secret-syncs/gcp-secret-manager/create",
"api-reference/endpoints/secret-syncs/gcp-secret-manager/update",
"api-reference/endpoints/secret-syncs/gcp-secret-manager/delete",
"api-reference/endpoints/secret-syncs/gcp-secret-manager/sync-secrets",
"api-reference/endpoints/secret-syncs/gcp-secret-manager/import-secrets",
"api-reference/endpoints/secret-syncs/gcp-secret-manager/remove-secrets"
]
},
{
"group": "GitHub",
"pages": [
"api-reference/endpoints/secret-syncs/github/list",
"api-reference/endpoints/secret-syncs/github/get-by-id",
"api-reference/endpoints/secret-syncs/github/get-by-name",
"api-reference/endpoints/secret-syncs/github/create",
"api-reference/endpoints/secret-syncs/github/update",
"api-reference/endpoints/secret-syncs/github/delete",
"api-reference/endpoints/secret-syncs/github/sync-secrets",
"api-reference/endpoints/secret-syncs/github/remove-secrets"
]
}
]
},
{

View File

@@ -0,0 +1,72 @@
import { Controller, useFormContext, useWatch } from "react-hook-form";
import { SingleValue } from "react-select";
import { faCircleInfo } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField";
import { FilterableSelect, FormControl, Tooltip } from "@app/components/v2";
import {
TDatabricksSecretScope,
useDatabricksConnectionListSecretScopes
} from "@app/hooks/api/appConnections/databricks";
import { SecretSync } from "@app/hooks/api/secretSyncs";
import { TSecretSyncForm } from "../schemas";
export const DatabricksSyncFields = () => {
const { control, setValue } = useFormContext<
TSecretSyncForm & { destination: SecretSync.Databricks }
>();
const connectionId = useWatch({ name: "connection.id", control });
const { data: secretScopes = [], isPending: isSecretScopesPending } =
useDatabricksConnectionListSecretScopes(connectionId, {
enabled: Boolean(connectionId)
});
return (
<>
<SecretSyncConnectionField
onChange={() => {
setValue("destinationConfig.scope", "");
}}
/>
<Controller
name="destinationConfig.scope"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
isError={Boolean(error)}
errorText={error?.message}
label="Secret Scope"
helperText={
<Tooltip
className="max-w-md"
content="Ensure that you've created the secret scope in the selected workspace, the service principal has been be assigned to the respective workspace and that your service principal has write permissions for the specified secret scope."
>
<div>
<span>Don&#39;t see the secret scope you&#39;re looking for?</span>{" "}
<FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-400" />
</div>
</Tooltip>
}
>
<FilterableSelect
isLoading={isSecretScopesPending && Boolean(connectionId)}
isDisabled={!connectionId}
value={secretScopes.find((scope) => scope.name === value) ?? null}
onChange={(option) =>
onChange((option as SingleValue<TDatabricksSecretScope>)?.name ?? null)
}
options={secretScopes}
placeholder="Select a secret scope..."
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.name}
/>
</FormControl>
)}
/>
</>
);
};

View File

@@ -1,5 +1,6 @@
import { useFormContext } from "react-hook-form";
import { DatabricksSyncFields } from "@app/components/secret-syncs/forms/SecretSyncDestinationFields/DatabricksSyncFields";
import { SecretSync } from "@app/hooks/api/secretSyncs";
import { TSecretSyncForm } from "../schemas";
@@ -28,6 +29,8 @@ export const SecretSyncDestinationFields = () => {
return <AzureKeyVaultSyncFields />;
case SecretSync.AzureAppConfiguration:
return <AzureAppConfigurationSyncFields />;
case SecretSync.Databricks:
return <DatabricksSyncFields />;
default:
throw new Error(`Unhandled Destination Config Field: ${destination}`);
}

View File

@@ -0,0 +1,12 @@
import { useFormContext } from "react-hook-form";
import { SecretSyncLabel } from "@app/components/secret-syncs";
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
import { SecretSync } from "@app/hooks/api/secretSyncs";
export const DatabricksSyncReviewFields = () => {
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.Databricks }>();
const scope = watch("destinationConfig.scope");
return <SecretSyncLabel label="Secret Scope">{scope}</SecretSyncLabel>;
};

View File

@@ -4,6 +4,7 @@ import { useFormContext } from "react-hook-form";
import { SecretSyncLabel } from "@app/components/secret-syncs";
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
import { AwsSecretsManagerSyncReviewFields } from "@app/components/secret-syncs/forms/SecretSyncReviewFields/AwsSecretsManagerSyncReviewFields";
import { DatabricksSyncReviewFields } from "@app/components/secret-syncs/forms/SecretSyncReviewFields/DatabricksSyncReviewFields";
import { Badge } from "@app/components/v2";
import { SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP, SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
import { SecretSync } from "@app/hooks/api/secretSyncs";
@@ -54,6 +55,9 @@ export const SecretSyncReviewFields = () => {
case SecretSync.AzureAppConfiguration:
DestinationFieldsComponent = <AzureAppConfigurationSyncReviewFields />;
break;
case SecretSync.Databricks:
DestinationFieldsComponent = <DatabricksSyncReviewFields />;
break;
default:
throw new Error(`Unhandled Destination Review Fields: ${destination}`);
}

View File

@@ -0,0 +1,10 @@
import { z } from "zod";
import { SecretSync } from "@app/hooks/api/secretSyncs";
export const DatabricksSyncDestinationSchema = z.object({
destination: z.literal(SecretSync.Databricks),
destinationConfig: z.object({
scope: z.string().trim().min(1, "Databricks scope required")
})
});

View File

@@ -1,6 +1,7 @@
import { z } from "zod";
import { AwsSecretsManagerSyncDestinationSchema } from "@app/components/secret-syncs/forms/schemas/aws-secrets-manager-sync-destination-schema";
import { DatabricksSyncDestinationSchema } from "@app/components/secret-syncs/forms/schemas/databricks-sync-destination-schema";
import { GitHubSyncDestinationSchema } from "@app/components/secret-syncs/forms/schemas/github-sync-destination-schema";
import { SecretSyncInitialSyncBehavior } from "@app/hooks/api/secretSyncs";
import { slugSchema } from "@app/lib/schemas";
@@ -39,7 +40,8 @@ const SecretSyncUnionSchema = z.discriminatedUnion("destination", [
GitHubSyncDestinationSchema,
GcpSyncDestinationSchema,
AzureKeyVaultSyncDestinationSchema,
AzureAppConfigurationSyncDestinationSchema
AzureAppConfigurationSyncDestinationSchema,
DatabricksSyncDestinationSchema
]);
export const SecretSyncFormSchema = SecretSyncUnionSchema.and(BaseSecretSyncSchema);

View File

@@ -10,6 +10,7 @@ import {
GitHubConnectionMethod,
TAppConnection
} from "@app/hooks/api/appConnections/types";
import { DatabricksConnectionMethod } from "@app/hooks/api/appConnections/types/databricks-connection";
export const APP_CONNECTION_MAP: Record<AppConnection, { name: string; image: string }> = {
[AppConnection.AWS]: { name: "AWS", image: "Amazon Web Services.png" },
@@ -22,7 +23,8 @@ export const APP_CONNECTION_MAP: Record<AppConnection, { name: string; image: st
[AppConnection.AzureAppConfiguration]: {
name: "Azure App Configuration",
image: "Microsoft Azure.png"
}
},
[AppConnection.Databricks]: { name: "Databricks", image: "Databricks.png" }
};
export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) => {
@@ -39,6 +41,8 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"])
return { name: "Assume Role", icon: faUser };
case GcpConnectionMethod.ServiceAccountImpersonation:
return { name: "Service Account Impersonation", icon: faUser };
case DatabricksConnectionMethod.ServicePrincipal:
return { name: "Service Principal", icon: faUser };
default:
throw new Error(`Unhandled App Connection Method: ${method}`);
}

View File

@@ -14,6 +14,10 @@ export const SECRET_SYNC_MAP: Record<SecretSync, { name: string; image: string }
[SecretSync.AzureAppConfiguration]: {
name: "Azure App Configuration",
image: "Microsoft Azure.png"
},
[SecretSync.Databricks]: {
name: "Databricks",
image: "Databricks.png"
}
};
@@ -23,7 +27,8 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
[SecretSync.GitHub]: AppConnection.GitHub,
[SecretSync.GCPSecretManager]: AppConnection.GCP,
[SecretSync.AzureKeyVault]: AppConnection.AzureKeyVault,
[SecretSync.AzureAppConfiguration]: AppConnection.AzureAppConfiguration
[SecretSync.AzureAppConfiguration]: AppConnection.AzureAppConfiguration,
[SecretSync.Databricks]: AppConnection.Databricks
};
export const SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP: Record<

View File

@@ -0,0 +1,2 @@
export * from "./queries";
export * from "./types";

View File

@@ -0,0 +1,37 @@
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { appConnectionKeys } from "@app/hooks/api/appConnections";
import { TDatabricksConnectionListSecretScopesResponse, TDatabricksSecretScope } from "./types";
const databricksConnectionKeys = {
all: [...appConnectionKeys.all, "databricks"] as const,
listSecretScopes: (connectionId: string) =>
[...databricksConnectionKeys.all, "workspace-scopes", connectionId] as const
};
export const useDatabricksConnectionListSecretScopes = (
connectionId: string,
options?: Omit<
UseQueryOptions<
TDatabricksSecretScope[],
unknown,
TDatabricksSecretScope[],
ReturnType<typeof databricksConnectionKeys.listSecretScopes>
>,
"queryKey" | "queryFn"
>
) => {
return useQuery({
queryKey: databricksConnectionKeys.listSecretScopes(connectionId),
queryFn: async () => {
const { data } = await apiRequest.get<TDatabricksConnectionListSecretScopesResponse>(
`/api/v1/app-connections/databricks/${connectionId}/secret-scopes`
);
return data.secretScopes;
},
...options
});
};

View File

@@ -0,0 +1,7 @@
export type TDatabricksSecretScope = {
name: string;
};
export type TDatabricksConnectionListSecretScopesResponse = {
secretScopes: TDatabricksSecretScope[];
};

View File

@@ -3,5 +3,6 @@ export enum AppConnection {
GitHub = "github",
GCP = "gcp",
AzureKeyVault = "azure-key-vault",
AzureAppConfiguration = "azure-app-configuration"
AzureAppConfiguration = "azure-app-configuration",
Databricks = "databricks"
}

View File

@@ -30,7 +30,17 @@ export type TAzureAppConfigurationConnectionOption = TAppConnectionOptionBase &
oauthClientId?: string;
};
export type TAppConnectionOption = TAwsConnectionOption | TGitHubConnectionOption;
export type TDatabricksConnectionOption = TAppConnectionOptionBase & {
app: AppConnection.Databricks;
};
export type TAppConnectionOption =
| TAwsConnectionOption
| TGitHubConnectionOption
| TGcpConnectionOption
| TAzureAppConfigurationConnectionOption
| TAzureKeyVaultConnectionOption
| TDatabricksConnectionOption;
export type TAppConnectionOptionMap = {
[AppConnection.AWS]: TAwsConnectionOption;
@@ -38,4 +48,5 @@ export type TAppConnectionOptionMap = {
[AppConnection.GCP]: TGcpConnectionOption;
[AppConnection.AzureKeyVault]: TAzureKeyVaultConnectionOption;
[AppConnection.AzureAppConfiguration]: TAzureAppConfigurationConnectionOption;
[AppConnection.Databricks]: TDatabricksConnectionOption;
};

View File

@@ -0,0 +1,15 @@
import { AppConnection } from "../enums";
import { TRootAppConnection } from "./root-connection";
export enum DatabricksConnectionMethod {
ServicePrincipal = "service-principal"
}
export type TDatabricksConnection = TRootAppConnection & { app: AppConnection.Databricks } & {
method: DatabricksConnectionMethod.ServicePrincipal;
credentials: {
workspaceUrl: string;
clientId: string;
clientSecret: string;
};
};

View File

@@ -1,6 +1,7 @@
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { TAppConnectionOption } from "@app/hooks/api/appConnections/types/app-options";
import { TAwsConnection } from "@app/hooks/api/appConnections/types/aws-connection";
import { TDatabricksConnection } from "@app/hooks/api/appConnections/types/databricks-connection";
import { TGitHubConnection } from "@app/hooks/api/appConnections/types/github-connection";
import { TAzureAppConfigurationConnection } from "./azure-app-configuration-connection";
@@ -18,7 +19,8 @@ export type TAppConnection =
| TGitHubConnection
| TGcpConnection
| TAzureKeyVaultConnection
| TAzureAppConfigurationConnection;
| TAzureAppConfigurationConnection
| TDatabricksConnection;
export type TAvailableAppConnection = Pick<TAppConnection, "name" | "id">;
@@ -51,4 +53,5 @@ export type TAppConnectionMap = {
[AppConnection.GCP]: TGcpConnection;
[AppConnection.AzureKeyVault]: TAzureKeyVaultConnection;
[AppConnection.AzureAppConfiguration]: TAzureAppConfigurationConnection;
[AppConnection.Databricks]: TDatabricksConnection;
};

View File

@@ -4,7 +4,8 @@ export enum SecretSync {
GitHub = "github",
GCPSecretManager = "gcp-secret-manager",
AzureKeyVault = "azure-key-vault",
AzureAppConfiguration = "azure-app-configuration"
AzureAppConfiguration = "azure-app-configuration",
Databricks = "databricks"
}
export enum SecretSyncStatus {

View File

@@ -0,0 +1,15 @@
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { SecretSync } from "@app/hooks/api/secretSyncs";
import { TRootSecretSync } from "@app/hooks/api/secretSyncs/types/root-sync";
export type TDatabricksSync = TRootSecretSync & {
destination: SecretSync.Databricks;
destinationConfig: {
scope: string;
};
connection: {
app: AppConnection.Databricks;
name: string;
id: string;
};
};

View File

@@ -1,5 +1,6 @@
import { SecretSync, SecretSyncImportBehavior } from "@app/hooks/api/secretSyncs";
import { TAwsParameterStoreSync } from "@app/hooks/api/secretSyncs/types/aws-parameter-store-sync";
import { TDatabricksSync } from "@app/hooks/api/secretSyncs/types/databricks-sync";
import { TGitHubSync } from "@app/hooks/api/secretSyncs/types/github-sync";
import { DiscriminativePick } from "@app/types";
@@ -20,7 +21,8 @@ export type TSecretSync =
| TGitHubSync
| TGcpSync
| TAzureKeyVaultSync
| TAzureAppConfigurationSync;
| TAzureAppConfigurationSync
| TDatabricksSync;
export type TListSecretSyncs = { secretSyncs: TSecretSync[] };

View File

@@ -12,6 +12,7 @@ import { AppConnectionHeader } from "../AppConnectionHeader";
import { AwsConnectionForm } from "./AwsConnectionForm";
import { AzureAppConfigurationConnectionForm } from "./AzureAppConfigurationConnectionForm";
import { AzureKeyVaultConnectionForm } from "./AzureKeyVaultConnectionForm";
import { DatabricksConnectionForm } from "./DatabricksConnectionForm";
import { GcpConnectionForm } from "./GcpConnectionForm";
import { GitHubConnectionForm } from "./GitHubConnectionForm";
@@ -59,6 +60,8 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => {
return <AzureKeyVaultConnectionForm />;
case AppConnection.AzureAppConfiguration:
return <AzureAppConfigurationConnectionForm />;
case AppConnection.Databricks:
return <DatabricksConnectionForm onSubmit={onSubmit} />;
default:
throw new Error(`Unhandled App ${app}`);
}
@@ -102,6 +105,8 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
return <AzureKeyVaultConnectionForm appConnection={appConnection} />;
case AppConnection.AzureAppConfiguration:
return <AzureAppConfigurationConnectionForm appConnection={appConnection} />;
case AppConnection.Databricks:
return <DatabricksConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
default:
throw new Error(`Unhandled App ${(appConnection as TAppConnection).app}`);
}

View File

@@ -0,0 +1,166 @@
import { Controller, FormProvider, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import {
Button,
FormControl,
Input,
ModalClose,
SecretInput,
Select,
SelectItem
} from "@app/components/v2";
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import {
DatabricksConnectionMethod,
TDatabricksConnection
} from "@app/hooks/api/appConnections/types/databricks-connection";
import {
genericAppConnectionFieldsSchema,
GenericAppConnectionsFields
} from "./GenericAppConnectionFields";
type Props = {
appConnection?: TDatabricksConnection;
onSubmit: (formData: FormData) => void;
};
const rootSchema = genericAppConnectionFieldsSchema.extend({
app: z.literal(AppConnection.Databricks)
});
const formSchema = z.discriminatedUnion("method", [
rootSchema.extend({
method: z.literal(DatabricksConnectionMethod.ServicePrincipal),
credentials: z.object({
workspaceUrl: z.string().url().trim().min(1, "Workspace URL required"),
clientId: z.string().trim().min(1, "Client ID required"),
clientSecret: z.string().trim().min(1, "Client secret required")
})
})
]);
type FormData = z.infer<typeof formSchema>;
export const DatabricksConnectionForm = ({ appConnection, onSubmit }: Props) => {
const isUpdate = Boolean(appConnection);
const form = useForm<FormData>({
resolver: zodResolver(formSchema),
defaultValues: appConnection ?? {
app: AppConnection.Databricks,
method: DatabricksConnectionMethod.ServicePrincipal
}
});
const {
handleSubmit,
control,
formState: { isSubmitting, isDirty }
} = form;
return (
<FormProvider {...form}>
<form onSubmit={handleSubmit(onSubmit)}>
{!isUpdate && <GenericAppConnectionsFields />}
<Controller
name="method"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
tooltipText={`The method you would like to use to connect with ${
APP_CONNECTION_MAP[AppConnection.Databricks].name
}. This field cannot be changed after creation.`}
errorText={error?.message}
isError={Boolean(error?.message)}
label="Method"
>
<Select
isDisabled={isUpdate}
value={value}
onValueChange={(val) => onChange(val)}
className="w-full border border-mineshaft-500"
position="popper"
dropdownContainerClassName="max-w-none"
>
{Object.values(DatabricksConnectionMethod).map((method) => {
return (
<SelectItem value={method} key={method}>
{getAppConnectionMethodDetails(method).name}
</SelectItem>
);
})}
</Select>
</FormControl>
)}
/>
<Controller
name="credentials.workspaceUrl"
control={control}
shouldUnregister
render={({ field, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="Workspace URL"
>
<Input {...field} placeholder="https://xxx-xxxxxxxx-xxx.cloud.databricks.com" />
</FormControl>
)}
/>
<Controller
name="credentials.clientId"
control={control}
shouldUnregister
render={({ field, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="Client ID"
>
<Input {...field} placeholder="05810c8f-c44d-4bd0-a327-aab6dd77719b" />
</FormControl>
)}
/>
<Controller
name="credentials.clientSecret"
control={control}
shouldUnregister
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="Client Secret"
>
<SecretInput
containerClassName="text-gray-400 group-focus-within:!border-primary-400/50 border border-mineshaft-500 bg-mineshaft-900 px-2.5 py-1.5"
value={value}
onChange={(e) => onChange(e.target.value)}
/>
</FormControl>
)}
/>
<div className="mt-8 flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
colorSchema="secondary"
isLoading={isSubmitting}
isDisabled={isSubmitting || !isDirty}
>
{isUpdate ? "Update Credentials" : "Connect to Databricks"}
</Button>
<ModalClose asChild>
<Button colorSchema="secondary" variant="plain">
Cancel
</Button>
</ModalClose>
</div>
</form>
</FormProvider>
);
};

View File

@@ -23,7 +23,7 @@ export const AppConnectionsSelect = ({ onSelect }: Props) => {
}
return (
<div className="grid grid-cols-5 gap-2">
<div className="grid grid-cols-4 gap-2">
{appConnectionOptions?.map((option) => (
<button
type="button"

View File

@@ -44,7 +44,7 @@ export const DeleteAppConnectionModal = ({ isOpen, onOpenChange, appConnection }
isOpen={isOpen}
onChange={onOpenChange}
title={`Are you sure want to delete ${name}?`}
deleteKey="confirm"
deleteKey={name}
onDeleteApproved={handleDeleteAppConnection}
/>
);

Some files were not shown because too many files have changed in this diff Show More