Add Bitbucket Secret Sync
@@ -2502,6 +2502,11 @@ export const SecretSyncs = {
|
|||||||
SUPABASE: {
|
SUPABASE: {
|
||||||
projectId: "The ID of the Supabase project to sync secrets to.",
|
projectId: "The ID of the Supabase project to sync secrets to.",
|
||||||
projectName: "The name of the Supabase project to sync secrets to."
|
projectName: "The name of the Supabase project to sync secrets to."
|
||||||
|
},
|
||||||
|
BITBUCKET: {
|
||||||
|
workspace: "The Bitbucket Workspace slug to sync secrets to.",
|
||||||
|
repository: "The Bitbucket Repository slug to sync secrets to.",
|
||||||
|
environment: "The Bitbucket Deployment Environment uuid to sync secrets to."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -85,4 +85,40 @@ export const registerBitbucketConnectionRouter = async (server: FastifyZodProvid
|
|||||||
return { repositories };
|
return { repositories };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: `/:connectionId/environments`,
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
connectionId: z.string().uuid()
|
||||||
|
}),
|
||||||
|
querystring: z.object({
|
||||||
|
workspaceSlug: z.string().min(1).max(255),
|
||||||
|
repositorySlug: z.string().min(1).max(255)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
environments: z.object({ slug: z.string(), name: z.string(), uuid: z.string() }).array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const {
|
||||||
|
params: { connectionId },
|
||||||
|
query: { workspaceSlug, repositorySlug }
|
||||||
|
} = req;
|
||||||
|
|
||||||
|
const environments = await server.services.appConnection.bitbucket.listEnvironments(
|
||||||
|
{ connectionId, workspaceSlug, repositorySlug },
|
||||||
|
req.permission
|
||||||
|
);
|
||||||
|
|
||||||
|
return { environments };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import {
|
||||||
|
BitbucketSyncSchema,
|
||||||
|
CreateBitbucketSyncSchema,
|
||||||
|
UpdateBitbucketSyncSchema
|
||||||
|
} from "@app/services/secret-sync/bitbucket";
|
||||||
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
|
||||||
|
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
|
||||||
|
|
||||||
|
export const registerBitbucketSyncRouter = async (server: FastifyZodProvider) =>
|
||||||
|
registerSyncSecretsEndpoints({
|
||||||
|
destination: SecretSync.Bitbucket,
|
||||||
|
server,
|
||||||
|
responseSchema: BitbucketSyncSchema,
|
||||||
|
createSchema: CreateBitbucketSyncSchema,
|
||||||
|
updateSchema: UpdateBitbucketSyncSchema
|
||||||
|
});
|
||||||
@@ -7,6 +7,7 @@ import { registerAwsSecretsManagerSyncRouter } from "./aws-secrets-manager-sync-
|
|||||||
import { registerAzureAppConfigurationSyncRouter } from "./azure-app-configuration-sync-router";
|
import { registerAzureAppConfigurationSyncRouter } from "./azure-app-configuration-sync-router";
|
||||||
import { registerAzureDevOpsSyncRouter } from "./azure-devops-sync-router";
|
import { registerAzureDevOpsSyncRouter } from "./azure-devops-sync-router";
|
||||||
import { registerAzureKeyVaultSyncRouter } from "./azure-key-vault-sync-router";
|
import { registerAzureKeyVaultSyncRouter } from "./azure-key-vault-sync-router";
|
||||||
|
import { registerBitbucketSyncRouter } from "./bitbucket-sync-router";
|
||||||
import { registerCamundaSyncRouter } from "./camunda-sync-router";
|
import { registerCamundaSyncRouter } from "./camunda-sync-router";
|
||||||
import { registerChecklySyncRouter } from "./checkly-sync-router";
|
import { registerChecklySyncRouter } from "./checkly-sync-router";
|
||||||
import { registerCloudflarePagesSyncRouter } from "./cloudflare-pages-sync-router";
|
import { registerCloudflarePagesSyncRouter } from "./cloudflare-pages-sync-router";
|
||||||
@@ -57,5 +58,6 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record<SecretSync, (server: Fastif
|
|||||||
[SecretSync.Supabase]: registerSupabaseSyncRouter,
|
[SecretSync.Supabase]: registerSupabaseSyncRouter,
|
||||||
[SecretSync.Zabbix]: registerZabbixSyncRouter,
|
[SecretSync.Zabbix]: registerZabbixSyncRouter,
|
||||||
[SecretSync.Railway]: registerRailwaySyncRouter,
|
[SecretSync.Railway]: registerRailwaySyncRouter,
|
||||||
[SecretSync.Checkly]: registerChecklySyncRouter
|
[SecretSync.Checkly]: registerChecklySyncRouter,
|
||||||
|
[SecretSync.Bitbucket]: registerBitbucketSyncRouter
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
} from "@app/services/secret-sync/azure-app-configuration";
|
} from "@app/services/secret-sync/azure-app-configuration";
|
||||||
import { AzureDevOpsSyncListItemSchema, AzureDevOpsSyncSchema } from "@app/services/secret-sync/azure-devops";
|
import { AzureDevOpsSyncListItemSchema, AzureDevOpsSyncSchema } from "@app/services/secret-sync/azure-devops";
|
||||||
import { AzureKeyVaultSyncListItemSchema, AzureKeyVaultSyncSchema } from "@app/services/secret-sync/azure-key-vault";
|
import { AzureKeyVaultSyncListItemSchema, AzureKeyVaultSyncSchema } from "@app/services/secret-sync/azure-key-vault";
|
||||||
|
import { BitbucketSyncListItemSchema, BitbucketSyncSchema } from "@app/services/secret-sync/bitbucket";
|
||||||
import { CamundaSyncListItemSchema, CamundaSyncSchema } from "@app/services/secret-sync/camunda";
|
import { CamundaSyncListItemSchema, CamundaSyncSchema } from "@app/services/secret-sync/camunda";
|
||||||
import { ChecklySyncListItemSchema, ChecklySyncSchema } from "@app/services/secret-sync/checkly/checkly-sync-schemas";
|
import { ChecklySyncListItemSchema, ChecklySyncSchema } from "@app/services/secret-sync/checkly/checkly-sync-schemas";
|
||||||
import {
|
import {
|
||||||
@@ -75,7 +76,8 @@ const SecretSyncSchema = z.discriminatedUnion("destination", [
|
|||||||
SupabaseSyncSchema,
|
SupabaseSyncSchema,
|
||||||
ZabbixSyncSchema,
|
ZabbixSyncSchema,
|
||||||
RailwaySyncSchema,
|
RailwaySyncSchema,
|
||||||
ChecklySyncSchema
|
ChecklySyncSchema,
|
||||||
|
BitbucketSyncSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
||||||
@@ -102,11 +104,11 @@ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
|||||||
GitLabSyncListItemSchema,
|
GitLabSyncListItemSchema,
|
||||||
CloudflarePagesSyncListItemSchema,
|
CloudflarePagesSyncListItemSchema,
|
||||||
CloudflareWorkersSyncListItemSchema,
|
CloudflareWorkersSyncListItemSchema,
|
||||||
|
|
||||||
ZabbixSyncListItemSchema,
|
ZabbixSyncListItemSchema,
|
||||||
RailwaySyncListItemSchema,
|
RailwaySyncListItemSchema,
|
||||||
ChecklySyncListItemSchema,
|
ChecklySyncListItemSchema,
|
||||||
SupabaseSyncListItemSchema
|
SupabaseSyncListItemSchema,
|
||||||
|
BitbucketSyncListItemSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const registerSecretSyncRouter = async (server: FastifyZodProvider) => {
|
export const registerSecretSyncRouter = async (server: FastifyZodProvider) => {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { BitbucketConnectionMethod } from "./bitbucket-connection-enums";
|
|||||||
import {
|
import {
|
||||||
TBitbucketConnection,
|
TBitbucketConnection,
|
||||||
TBitbucketConnectionConfig,
|
TBitbucketConnectionConfig,
|
||||||
|
TBitbucketEnvironment,
|
||||||
TBitbucketRepo,
|
TBitbucketRepo,
|
||||||
TBitbucketWorkspace
|
TBitbucketWorkspace
|
||||||
} from "./bitbucket-connection-types";
|
} from "./bitbucket-connection-types";
|
||||||
@@ -115,3 +116,40 @@ export const listBitbucketRepositories = async (appConnection: TBitbucketConnect
|
|||||||
|
|
||||||
return allRepos;
|
return allRepos;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const listBitbucketEnvironments = async (
|
||||||
|
appConnection: TBitbucketConnection,
|
||||||
|
workspaceSlug: string,
|
||||||
|
repositorySlug: string
|
||||||
|
) => {
|
||||||
|
const { email, apiToken } = appConnection.credentials;
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
Authorization: `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`,
|
||||||
|
Accept: "application/json"
|
||||||
|
};
|
||||||
|
|
||||||
|
const environments: TBitbucketEnvironment[] = [];
|
||||||
|
let hasNextPage = true;
|
||||||
|
|
||||||
|
let environmentsUrl = `${IntegrationUrls.BITBUCKET_API_URL}/2.0/repositories/${encodeURIComponent(workspaceSlug)}/${encodeURIComponent(repositorySlug)}/environments?pagelen=100`;
|
||||||
|
|
||||||
|
while (hasNextPage) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const { data }: { data: { values: TBitbucketEnvironment[]; next: string } } = await request.get(environmentsUrl, {
|
||||||
|
headers
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data?.values.length > 0) {
|
||||||
|
environments.push(...data.values);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.next) {
|
||||||
|
environmentsUrl = data.next;
|
||||||
|
} else {
|
||||||
|
hasNextPage = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return environments;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
import { OrgServiceActor } from "@app/lib/types";
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
|
|
||||||
import { AppConnection } from "../app-connection-enums";
|
import { AppConnection } from "../app-connection-enums";
|
||||||
import { listBitbucketRepositories, listBitbucketWorkspaces } from "./bitbucket-connection-fns";
|
import {
|
||||||
import { TBitbucketConnection, TGetBitbucketRepositoriesDTO } from "./bitbucket-connection-types";
|
listBitbucketEnvironments,
|
||||||
|
listBitbucketRepositories,
|
||||||
|
listBitbucketWorkspaces
|
||||||
|
} from "./bitbucket-connection-fns";
|
||||||
|
import {
|
||||||
|
TBitbucketConnection,
|
||||||
|
TGetBitbucketEnvironmentsDTO,
|
||||||
|
TGetBitbucketRepositoriesDTO
|
||||||
|
} from "./bitbucket-connection-types";
|
||||||
|
|
||||||
type TGetAppConnectionFunc = (
|
type TGetAppConnectionFunc = (
|
||||||
app: AppConnection,
|
app: AppConnection,
|
||||||
@@ -26,8 +34,18 @@ export const bitbucketConnectionService = (getAppConnection: TGetAppConnectionFu
|
|||||||
return repositories;
|
return repositories;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const listEnvironments = async (
|
||||||
|
{ connectionId, workspaceSlug, repositorySlug }: TGetBitbucketEnvironmentsDTO,
|
||||||
|
actor: OrgServiceActor
|
||||||
|
) => {
|
||||||
|
const appConnection = await getAppConnection(AppConnection.Bitbucket, connectionId, actor);
|
||||||
|
const environments = await listBitbucketEnvironments(appConnection, workspaceSlug, repositorySlug);
|
||||||
|
return environments;
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
listWorkspaces,
|
listWorkspaces,
|
||||||
listRepositories
|
listRepositories,
|
||||||
|
listEnvironments
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -38,3 +38,20 @@ export type TBitbucketRepo = {
|
|||||||
full_name: string; // workspace-slug/repo-slug
|
full_name: string; // workspace-slug/repo-slug
|
||||||
slug: string;
|
slug: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TGetBitbucketEnvironmentsDTO = {
|
||||||
|
connectionId: string;
|
||||||
|
workspaceSlug: string;
|
||||||
|
repositorySlug: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TBitbucketEnvironment = {
|
||||||
|
uuid: string;
|
||||||
|
slug: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BitbucketEnvironmentsResponse = {
|
||||||
|
values: TBitbucketEnvironment[];
|
||||||
|
next?: string;
|
||||||
|
};
|
||||||
|
|||||||
@@ -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 BITBUCKET_SYNC_LIST_OPTION: TSecretSyncListItem = {
|
||||||
|
name: "Bitbucket",
|
||||||
|
destination: SecretSync.Bitbucket,
|
||||||
|
connection: AppConnection.Bitbucket,
|
||||||
|
canImportSecrets: true
|
||||||
|
};
|
||||||
@@ -0,0 +1,380 @@
|
|||||||
|
import { request } from "@app/lib/config/request";
|
||||||
|
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
||||||
|
import {
|
||||||
|
TBitbucketListVariables,
|
||||||
|
TBitbucketSyncWithCredentials,
|
||||||
|
TBitbucketVariable,
|
||||||
|
TDeleteBitbucketVariable,
|
||||||
|
TPutBitbucketVariable
|
||||||
|
} from "@app/services/secret-sync/bitbucket/bitbucket-sync-types";
|
||||||
|
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
|
||||||
|
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
|
||||||
|
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
|
||||||
|
|
||||||
|
const listBitbucketSecrets = async ({ email, apiToken, workspace, repository }: TBitbucketListVariables) => {
|
||||||
|
const { data } = await request.get<{ values: TBitbucketVariable[] }>(
|
||||||
|
`${IntegrationUrls.BITBUCKET_API_URL}/2.0/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repository)}/pipelines_config/variables/`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`,
|
||||||
|
Accept: "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return data.values;
|
||||||
|
};
|
||||||
|
|
||||||
|
const listBitbucketEnvironmentSecrets = async ({
|
||||||
|
email,
|
||||||
|
apiToken,
|
||||||
|
workspace,
|
||||||
|
repository,
|
||||||
|
environment
|
||||||
|
}: TBitbucketListVariables & { environment: string }) => {
|
||||||
|
const { data } = await request.get<{ values: TBitbucketVariable[] }>(
|
||||||
|
`${IntegrationUrls.BITBUCKET_API_URL}/2.0/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repository)}/deployments_config/environments/${environment}/variables`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`,
|
||||||
|
Accept: "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return data.values;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to upsert a single variable
|
||||||
|
const upsertBitbucketVariable = async ({
|
||||||
|
email,
|
||||||
|
apiToken,
|
||||||
|
workspace,
|
||||||
|
repository,
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
existingVariables,
|
||||||
|
isEnvironment = false,
|
||||||
|
environment
|
||||||
|
}: {
|
||||||
|
email: string;
|
||||||
|
apiToken: string;
|
||||||
|
workspace: string;
|
||||||
|
repository: string;
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
existingVariables: TBitbucketVariable[];
|
||||||
|
isEnvironment?: boolean;
|
||||||
|
environment?: string;
|
||||||
|
}) => {
|
||||||
|
const existingVariable = existingVariables.find((variable) => variable.key === key);
|
||||||
|
const auth = `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`;
|
||||||
|
|
||||||
|
if (existingVariable) {
|
||||||
|
// Variable exists, use PUT to update it
|
||||||
|
const baseUrl = `${IntegrationUrls.BITBUCKET_API_URL}/2.0/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repository)}`;
|
||||||
|
const url = isEnvironment
|
||||||
|
? `${baseUrl}/deployments_config/environments/${environment}/variables/${existingVariable.uuid}`
|
||||||
|
: `${baseUrl}/pipelines_config/variables/${existingVariable.uuid}`;
|
||||||
|
|
||||||
|
return request.put(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
secured: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: auth,
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseUrl = `${IntegrationUrls.BITBUCKET_API_URL}/2.0/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repository)}`;
|
||||||
|
const url = isEnvironment
|
||||||
|
? `${baseUrl}/deployments_config/environments/${environment}/variables`
|
||||||
|
: `${baseUrl}/pipelines_config/variables/`;
|
||||||
|
|
||||||
|
return request.post(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
secured: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: auth,
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const putBitbucketSecrets = async ({
|
||||||
|
email,
|
||||||
|
apiToken,
|
||||||
|
workspace,
|
||||||
|
repository,
|
||||||
|
secretMap
|
||||||
|
}: TPutBitbucketVariable & { secretMap: TSecretMap }) => {
|
||||||
|
// Get existing variables first
|
||||||
|
const existingVariables = await listBitbucketSecrets({ email, apiToken, workspace, repository });
|
||||||
|
|
||||||
|
const promises = Object.entries(secretMap).map(([key, { value }]) => {
|
||||||
|
return upsertBitbucketVariable({
|
||||||
|
email,
|
||||||
|
apiToken,
|
||||||
|
workspace,
|
||||||
|
repository,
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
existingVariables,
|
||||||
|
isEnvironment: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
};
|
||||||
|
|
||||||
|
const putBitbucketEnvironmentSecrets = async ({
|
||||||
|
email,
|
||||||
|
apiToken,
|
||||||
|
workspace,
|
||||||
|
repository,
|
||||||
|
environment,
|
||||||
|
secretMap
|
||||||
|
}: TPutBitbucketVariable & { environment: string; secretMap: TSecretMap }) => {
|
||||||
|
// Get existing variables first
|
||||||
|
const existingVariables = await listBitbucketEnvironmentSecrets({
|
||||||
|
email,
|
||||||
|
apiToken,
|
||||||
|
workspace,
|
||||||
|
repository,
|
||||||
|
environment
|
||||||
|
});
|
||||||
|
|
||||||
|
const promises = Object.entries(secretMap).map(([key, { value }]) => {
|
||||||
|
return upsertBitbucketVariable({
|
||||||
|
email,
|
||||||
|
apiToken,
|
||||||
|
workspace,
|
||||||
|
repository,
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
existingVariables,
|
||||||
|
isEnvironment: true,
|
||||||
|
environment
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteBitbucketSecrets = async ({ email, apiToken, workspace, repository, keys }: TDeleteBitbucketVariable) => {
|
||||||
|
// First, we need to get the variable UUIDs since Bitbucket requires UUIDs for deletion
|
||||||
|
const existingVariables = await listBitbucketSecrets({ email, apiToken, workspace, repository });
|
||||||
|
const variablesToDelete = existingVariables.filter((variable) => keys.includes(variable.key));
|
||||||
|
|
||||||
|
const promises = variablesToDelete.map((variable) => {
|
||||||
|
return request.delete(
|
||||||
|
`${IntegrationUrls.BITBUCKET_API_URL}/2.0/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repository)}/pipelines_config/variables/${variable.uuid}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteBitbucketEnvironmentSecrets = async ({
|
||||||
|
email,
|
||||||
|
apiToken,
|
||||||
|
workspace,
|
||||||
|
repository,
|
||||||
|
environment,
|
||||||
|
keys
|
||||||
|
}: TDeleteBitbucketVariable & { environment: string }) => {
|
||||||
|
// Get the variable UUIDs since Bitbucket requires UUIDs for deletion
|
||||||
|
const existingVariables = await listBitbucketEnvironmentSecrets({
|
||||||
|
email,
|
||||||
|
apiToken,
|
||||||
|
workspace,
|
||||||
|
repository,
|
||||||
|
environment
|
||||||
|
});
|
||||||
|
const variablesToDelete = existingVariables.filter((variable) => keys.includes(variable.key));
|
||||||
|
|
||||||
|
const promises = variablesToDelete.map((variable) => {
|
||||||
|
return request.delete(
|
||||||
|
`${IntegrationUrls.BITBUCKET_API_URL}/2.0/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repository)}/deployments_config/environments/${environment}/variables/${variable.uuid}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BitbucketSyncFns = {
|
||||||
|
syncSecrets: async (secretSync: TBitbucketSyncWithCredentials, secretMap: TSecretMap) => {
|
||||||
|
const {
|
||||||
|
connection,
|
||||||
|
environment,
|
||||||
|
destinationConfig: { workspace, repository, environment: configEnvironment }
|
||||||
|
} = secretSync;
|
||||||
|
|
||||||
|
const { email, apiToken } = connection.credentials;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// If environment is specified in destinationConfig, use environment variables
|
||||||
|
if (configEnvironment) {
|
||||||
|
await putBitbucketEnvironmentSecrets({
|
||||||
|
email,
|
||||||
|
apiToken,
|
||||||
|
workspace,
|
||||||
|
repository,
|
||||||
|
environment: configEnvironment,
|
||||||
|
secretMap
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Otherwise, use repository variables (original behavior)
|
||||||
|
await putBitbucketSecrets({ email, apiToken, workspace, repository, secretMap });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (secretSync.syncOptions.disableSecretDeletion) return;
|
||||||
|
|
||||||
|
// Get existing secrets based on whether we're using environment or repository variables
|
||||||
|
const existingVariables = configEnvironment
|
||||||
|
? await listBitbucketEnvironmentSecrets({
|
||||||
|
email,
|
||||||
|
apiToken,
|
||||||
|
workspace,
|
||||||
|
repository,
|
||||||
|
environment: configEnvironment
|
||||||
|
})
|
||||||
|
: await listBitbucketSecrets({ email, apiToken, workspace, repository });
|
||||||
|
|
||||||
|
const keys = existingVariables
|
||||||
|
.map((variable) => variable.key)
|
||||||
|
.filter(
|
||||||
|
(secret) =>
|
||||||
|
matchesSchema(secret, environment?.slug || "", secretSync.syncOptions.keySchema) && !(secret in secretMap)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (keys.length > 0) {
|
||||||
|
try {
|
||||||
|
if (configEnvironment) {
|
||||||
|
await deleteBitbucketEnvironmentSecrets({
|
||||||
|
email,
|
||||||
|
apiToken,
|
||||||
|
workspace,
|
||||||
|
repository,
|
||||||
|
environment: configEnvironment,
|
||||||
|
keys
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await deleteBitbucketSecrets({ email, apiToken, workspace, repository, keys });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeSecrets: async (secretSync: TBitbucketSyncWithCredentials, secretMap: TSecretMap) => {
|
||||||
|
const {
|
||||||
|
connection,
|
||||||
|
destinationConfig: { workspace, repository, environment: configEnvironment }
|
||||||
|
} = secretSync;
|
||||||
|
|
||||||
|
const { email, apiToken } = connection.credentials;
|
||||||
|
|
||||||
|
const existingVariables = configEnvironment
|
||||||
|
? await listBitbucketEnvironmentSecrets({
|
||||||
|
email,
|
||||||
|
apiToken,
|
||||||
|
workspace,
|
||||||
|
repository,
|
||||||
|
environment: configEnvironment
|
||||||
|
})
|
||||||
|
: await listBitbucketSecrets({ email, apiToken, workspace, repository });
|
||||||
|
|
||||||
|
const keys = existingVariables.map((variable) => variable.key).filter((secret) => secret in secretMap);
|
||||||
|
|
||||||
|
if (keys.length > 0) {
|
||||||
|
try {
|
||||||
|
if (configEnvironment) {
|
||||||
|
await deleteBitbucketEnvironmentSecrets({
|
||||||
|
email,
|
||||||
|
apiToken,
|
||||||
|
workspace,
|
||||||
|
repository,
|
||||||
|
environment: configEnvironment,
|
||||||
|
keys
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await deleteBitbucketSecrets({ email, apiToken, workspace, repository, keys });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getSecrets: async (secretSync: TBitbucketSyncWithCredentials) => {
|
||||||
|
const {
|
||||||
|
connection,
|
||||||
|
destinationConfig: { workspace, repository, environment }
|
||||||
|
} = secretSync;
|
||||||
|
|
||||||
|
const { email, apiToken } = connection.credentials;
|
||||||
|
|
||||||
|
try {
|
||||||
|
let url: string;
|
||||||
|
|
||||||
|
if (environment) {
|
||||||
|
url = `${IntegrationUrls.BITBUCKET_API_URL}/2.0/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repository)}/deployments_config/environments/${environment}/variables`;
|
||||||
|
} else {
|
||||||
|
url = `${IntegrationUrls.BITBUCKET_API_URL}/2.0/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repository)}/pipelines_config/variables/`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await request.get<{ values: TBitbucketVariable[] }>(url, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`,
|
||||||
|
Accept: "application/json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const secretMap: TSecretMap = {};
|
||||||
|
data.values.forEach((variable) => {
|
||||||
|
secretMap[variable.key] = {
|
||||||
|
value: variable.secured ? "[SECURED]" : variable.value || "",
|
||||||
|
comment: ""
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return secretMap;
|
||||||
|
} catch (error) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
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 BitbucketSyncDestinationConfigSchema = z.object({
|
||||||
|
repository: z.string().describe(SecretSyncs.DESTINATION_CONFIG.BITBUCKET.repository),
|
||||||
|
environment: z.string().optional().describe(SecretSyncs.DESTINATION_CONFIG.BITBUCKET.environment),
|
||||||
|
workspace: z.string().describe(SecretSyncs.DESTINATION_CONFIG.BITBUCKET.workspace)
|
||||||
|
});
|
||||||
|
|
||||||
|
const BitbucketSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };
|
||||||
|
|
||||||
|
export const BitbucketSyncSchema = BaseSecretSyncSchema(SecretSync.Bitbucket, BitbucketSyncOptionsConfig).extend({
|
||||||
|
destination: z.literal(SecretSync.Bitbucket),
|
||||||
|
destinationConfig: BitbucketSyncDestinationConfigSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateBitbucketSyncSchema = GenericCreateSecretSyncFieldsSchema(
|
||||||
|
SecretSync.Bitbucket,
|
||||||
|
BitbucketSyncOptionsConfig
|
||||||
|
).extend({
|
||||||
|
destinationConfig: BitbucketSyncDestinationConfigSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateBitbucketSyncSchema = GenericUpdateSecretSyncFieldsSchema(
|
||||||
|
SecretSync.Bitbucket,
|
||||||
|
BitbucketSyncOptionsConfig
|
||||||
|
).extend({
|
||||||
|
destinationConfig: BitbucketSyncDestinationConfigSchema.optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const BitbucketSyncListItemSchema = z.object({
|
||||||
|
name: z.literal("Bitbucket"),
|
||||||
|
connection: z.literal(AppConnection.Bitbucket),
|
||||||
|
destination: z.literal(SecretSync.Bitbucket),
|
||||||
|
canImportSecrets: z.literal(true)
|
||||||
|
});
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TBitbucketConnection } from "@app/services/app-connection/bitbucket";
|
||||||
|
|
||||||
|
import { BitbucketSyncListItemSchema, BitbucketSyncSchema, CreateBitbucketSyncSchema } from "./bitbucket-sync-schemas";
|
||||||
|
|
||||||
|
export type TBitbucketSync = z.infer<typeof BitbucketSyncSchema>;
|
||||||
|
|
||||||
|
export type TBitbucketSyncInput = z.infer<typeof CreateBitbucketSyncSchema>;
|
||||||
|
|
||||||
|
export type TBitbucketSyncListItem = z.infer<typeof BitbucketSyncListItemSchema>;
|
||||||
|
|
||||||
|
export type TBitbucketSyncWithCredentials = TBitbucketSync & {
|
||||||
|
connection: TBitbucketConnection;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TBitbucketVariable = {
|
||||||
|
key: string;
|
||||||
|
value?: string;
|
||||||
|
secured: boolean;
|
||||||
|
uuid: string;
|
||||||
|
type: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TBitbucketListVariables = {
|
||||||
|
apiToken: string;
|
||||||
|
email: string;
|
||||||
|
workspace: string;
|
||||||
|
repository: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TPutBitbucketVariable = {
|
||||||
|
email: string;
|
||||||
|
apiToken: string;
|
||||||
|
workspace: string;
|
||||||
|
repository: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TDeleteBitbucketVariable = {
|
||||||
|
email: string;
|
||||||
|
apiToken: string;
|
||||||
|
workspace: string;
|
||||||
|
repository: string;
|
||||||
|
keys: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TBitbucketConnectionCredentials = {
|
||||||
|
email: string;
|
||||||
|
apiToken: string;
|
||||||
|
};
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export * from "./bitbucket-sync-constants";
|
||||||
|
export * from "./bitbucket-sync-fns";
|
||||||
|
export * from "./bitbucket-sync-schemas";
|
||||||
|
export * from "./bitbucket-sync-types";
|
||||||
@@ -25,7 +25,8 @@ export enum SecretSync {
|
|||||||
Supabase = "supabase",
|
Supabase = "supabase",
|
||||||
Zabbix = "zabbix",
|
Zabbix = "zabbix",
|
||||||
Railway = "railway",
|
Railway = "railway",
|
||||||
Checkly = "checkly"
|
Checkly = "checkly",
|
||||||
|
Bitbucket = "bitbucket"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SecretSyncInitialSyncBehavior {
|
export enum SecretSyncInitialSyncBehavior {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import { ONEPASS_SYNC_LIST_OPTION, OnePassSyncFns } from "./1password";
|
|||||||
import { AZURE_APP_CONFIGURATION_SYNC_LIST_OPTION, azureAppConfigurationSyncFactory } from "./azure-app-configuration";
|
import { AZURE_APP_CONFIGURATION_SYNC_LIST_OPTION, azureAppConfigurationSyncFactory } from "./azure-app-configuration";
|
||||||
import { AZURE_DEVOPS_SYNC_LIST_OPTION, azureDevOpsSyncFactory } from "./azure-devops";
|
import { AZURE_DEVOPS_SYNC_LIST_OPTION, azureDevOpsSyncFactory } from "./azure-devops";
|
||||||
import { AZURE_KEY_VAULT_SYNC_LIST_OPTION, azureKeyVaultSyncFactory } from "./azure-key-vault";
|
import { AZURE_KEY_VAULT_SYNC_LIST_OPTION, azureKeyVaultSyncFactory } from "./azure-key-vault";
|
||||||
|
import { BITBUCKET_SYNC_LIST_OPTION, BitbucketSyncFns } from "./bitbucket";
|
||||||
import { CAMUNDA_SYNC_LIST_OPTION, camundaSyncFactory } from "./camunda";
|
import { CAMUNDA_SYNC_LIST_OPTION, camundaSyncFactory } from "./camunda";
|
||||||
import { CHECKLY_SYNC_LIST_OPTION } from "./checkly/checkly-sync-constants";
|
import { CHECKLY_SYNC_LIST_OPTION } from "./checkly/checkly-sync-constants";
|
||||||
import { ChecklySyncFns } from "./checkly/checkly-sync-fns";
|
import { ChecklySyncFns } from "./checkly/checkly-sync-fns";
|
||||||
@@ -80,7 +81,8 @@ const SECRET_SYNC_LIST_OPTIONS: Record<SecretSync, TSecretSyncListItem> = {
|
|||||||
[SecretSync.Supabase]: SUPABASE_SYNC_LIST_OPTION,
|
[SecretSync.Supabase]: SUPABASE_SYNC_LIST_OPTION,
|
||||||
[SecretSync.Zabbix]: ZABBIX_SYNC_LIST_OPTION,
|
[SecretSync.Zabbix]: ZABBIX_SYNC_LIST_OPTION,
|
||||||
[SecretSync.Railway]: RAILWAY_SYNC_LIST_OPTION,
|
[SecretSync.Railway]: RAILWAY_SYNC_LIST_OPTION,
|
||||||
[SecretSync.Checkly]: CHECKLY_SYNC_LIST_OPTION
|
[SecretSync.Checkly]: CHECKLY_SYNC_LIST_OPTION,
|
||||||
|
[SecretSync.Bitbucket]: BITBUCKET_SYNC_LIST_OPTION
|
||||||
};
|
};
|
||||||
|
|
||||||
export const listSecretSyncOptions = () => {
|
export const listSecretSyncOptions = () => {
|
||||||
@@ -258,6 +260,8 @@ export const SecretSyncFns = {
|
|||||||
return ChecklySyncFns.syncSecrets(secretSync, schemaSecretMap);
|
return ChecklySyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||||
case SecretSync.Supabase:
|
case SecretSync.Supabase:
|
||||||
return SupabaseSyncFns.syncSecrets(secretSync, schemaSecretMap);
|
return SupabaseSyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||||
|
case SecretSync.Bitbucket:
|
||||||
|
return BitbucketSyncFns.syncSecrets(secretSync, schemaSecretMap);
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unhandled sync destination for sync secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
`Unhandled sync destination for sync secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
||||||
@@ -365,6 +369,9 @@ export const SecretSyncFns = {
|
|||||||
case SecretSync.Supabase:
|
case SecretSync.Supabase:
|
||||||
secretMap = await SupabaseSyncFns.getSecrets(secretSync);
|
secretMap = await SupabaseSyncFns.getSecrets(secretSync);
|
||||||
break;
|
break;
|
||||||
|
case SecretSync.Bitbucket:
|
||||||
|
secretMap = await BitbucketSyncFns.getSecrets(secretSync);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unhandled sync destination for get secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
`Unhandled sync destination for get secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
||||||
@@ -452,6 +459,8 @@ export const SecretSyncFns = {
|
|||||||
return ChecklySyncFns.removeSecrets(secretSync, schemaSecretMap);
|
return ChecklySyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||||
case SecretSync.Supabase:
|
case SecretSync.Supabase:
|
||||||
return SupabaseSyncFns.removeSecrets(secretSync, schemaSecretMap);
|
return SupabaseSyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||||
|
case SecretSync.Bitbucket:
|
||||||
|
return BitbucketSyncFns.removeSecrets(secretSync, schemaSecretMap);
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unhandled sync destination for remove secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
`Unhandled sync destination for remove secrets fns: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
|
|||||||
[SecretSync.Supabase]: "Supabase",
|
[SecretSync.Supabase]: "Supabase",
|
||||||
[SecretSync.Zabbix]: "Zabbix",
|
[SecretSync.Zabbix]: "Zabbix",
|
||||||
[SecretSync.Railway]: "Railway",
|
[SecretSync.Railway]: "Railway",
|
||||||
[SecretSync.Checkly]: "Checkly"
|
[SecretSync.Checkly]: "Checkly",
|
||||||
|
[SecretSync.Bitbucket]: "Bitbucket"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
||||||
@@ -58,7 +59,8 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
|||||||
[SecretSync.Supabase]: AppConnection.Supabase,
|
[SecretSync.Supabase]: AppConnection.Supabase,
|
||||||
[SecretSync.Zabbix]: AppConnection.Zabbix,
|
[SecretSync.Zabbix]: AppConnection.Zabbix,
|
||||||
[SecretSync.Railway]: AppConnection.Railway,
|
[SecretSync.Railway]: AppConnection.Railway,
|
||||||
[SecretSync.Checkly]: AppConnection.Checkly
|
[SecretSync.Checkly]: AppConnection.Checkly,
|
||||||
|
[SecretSync.Bitbucket]: AppConnection.Bitbucket
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SECRET_SYNC_PLAN_MAP: Record<SecretSync, SecretSyncPlanType> = {
|
export const SECRET_SYNC_PLAN_MAP: Record<SecretSync, SecretSyncPlanType> = {
|
||||||
@@ -88,5 +90,6 @@ export const SECRET_SYNC_PLAN_MAP: Record<SecretSync, SecretSyncPlanType> = {
|
|||||||
[SecretSync.Supabase]: SecretSyncPlanType.Regular,
|
[SecretSync.Supabase]: SecretSyncPlanType.Regular,
|
||||||
[SecretSync.Zabbix]: SecretSyncPlanType.Regular,
|
[SecretSync.Zabbix]: SecretSyncPlanType.Regular,
|
||||||
[SecretSync.Railway]: SecretSyncPlanType.Regular,
|
[SecretSync.Railway]: SecretSyncPlanType.Regular,
|
||||||
[SecretSync.Checkly]: SecretSyncPlanType.Regular
|
[SecretSync.Checkly]: SecretSyncPlanType.Regular,
|
||||||
|
[SecretSync.Bitbucket]: SecretSyncPlanType.Regular
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -72,6 +72,12 @@ import {
|
|||||||
TAzureKeyVaultSyncListItem,
|
TAzureKeyVaultSyncListItem,
|
||||||
TAzureKeyVaultSyncWithCredentials
|
TAzureKeyVaultSyncWithCredentials
|
||||||
} from "./azure-key-vault";
|
} from "./azure-key-vault";
|
||||||
|
import {
|
||||||
|
TBitbucketSync,
|
||||||
|
TBitbucketSyncInput,
|
||||||
|
TBitbucketSyncListItem,
|
||||||
|
TBitbucketSyncWithCredentials
|
||||||
|
} from "./bitbucket/bitbucket-sync-types";
|
||||||
import {
|
import {
|
||||||
TChecklySync,
|
TChecklySync,
|
||||||
TChecklySyncInput,
|
TChecklySyncInput,
|
||||||
@@ -166,7 +172,8 @@ export type TSecretSync =
|
|||||||
| TZabbixSync
|
| TZabbixSync
|
||||||
| TRailwaySync
|
| TRailwaySync
|
||||||
| TChecklySync
|
| TChecklySync
|
||||||
| TSupabaseSync;
|
| TSupabaseSync
|
||||||
|
| TBitbucketSync;
|
||||||
|
|
||||||
export type TSecretSyncWithCredentials =
|
export type TSecretSyncWithCredentials =
|
||||||
| TAwsParameterStoreSyncWithCredentials
|
| TAwsParameterStoreSyncWithCredentials
|
||||||
@@ -195,7 +202,8 @@ export type TSecretSyncWithCredentials =
|
|||||||
| TZabbixSyncWithCredentials
|
| TZabbixSyncWithCredentials
|
||||||
| TRailwaySyncWithCredentials
|
| TRailwaySyncWithCredentials
|
||||||
| TChecklySyncWithCredentials
|
| TChecklySyncWithCredentials
|
||||||
| TSupabaseSyncWithCredentials;
|
| TSupabaseSyncWithCredentials
|
||||||
|
| TBitbucketSyncWithCredentials;
|
||||||
|
|
||||||
export type TSecretSyncInput =
|
export type TSecretSyncInput =
|
||||||
| TAwsParameterStoreSyncInput
|
| TAwsParameterStoreSyncInput
|
||||||
@@ -224,7 +232,8 @@ export type TSecretSyncInput =
|
|||||||
| TZabbixSyncInput
|
| TZabbixSyncInput
|
||||||
| TRailwaySyncInput
|
| TRailwaySyncInput
|
||||||
| TChecklySyncInput
|
| TChecklySyncInput
|
||||||
| TSupabaseSyncInput;
|
| TSupabaseSyncInput
|
||||||
|
| TBitbucketSyncInput;
|
||||||
|
|
||||||
export type TSecretSyncListItem =
|
export type TSecretSyncListItem =
|
||||||
| TAwsParameterStoreSyncListItem
|
| TAwsParameterStoreSyncListItem
|
||||||
@@ -253,7 +262,8 @@ export type TSecretSyncListItem =
|
|||||||
| TZabbixSyncListItem
|
| TZabbixSyncListItem
|
||||||
| TRailwaySyncListItem
|
| TRailwaySyncListItem
|
||||||
| TChecklySyncListItem
|
| TChecklySyncListItem
|
||||||
| TSupabaseSyncListItem;
|
| TSupabaseSyncListItem
|
||||||
|
| TBitbucketSyncListItem;
|
||||||
|
|
||||||
export type TSyncOptionsConfig = {
|
export type TSyncOptionsConfig = {
|
||||||
canImportSecrets: boolean;
|
canImportSecrets: boolean;
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Create"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/bitbucket"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Delete"
|
||||||
|
openapi: "DELETE /api/v1/secret-syncs/bitbucket/{syncId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by ID"
|
||||||
|
openapi: "GET /api/v1/secret-syncs/bitbucket/{syncId}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Get by Name"
|
||||||
|
openapi: "GET /api/v1/secret-syncs/bitbucket/sync-name/{syncName}"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Import Secrets"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/bitbucket/{syncId}/import-secrets"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "List"
|
||||||
|
openapi: "GET /api/v1/secret-syncs/bitbucket"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Remove Secrets"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/bitbucket/{syncId}/remove-secrets"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Sync Secrets"
|
||||||
|
openapi: "POST /api/v1/secret-syncs/bitbucket/{syncId}/sync-secrets"
|
||||||
|
---
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
title: "Update"
|
||||||
|
openapi: "PATCH /api/v1/secret-syncs/bitbucket/{syncId}"
|
||||||
|
---
|
||||||
@@ -508,6 +508,7 @@
|
|||||||
"integrations/secret-syncs/azure-app-configuration",
|
"integrations/secret-syncs/azure-app-configuration",
|
||||||
"integrations/secret-syncs/azure-devops",
|
"integrations/secret-syncs/azure-devops",
|
||||||
"integrations/secret-syncs/azure-key-vault",
|
"integrations/secret-syncs/azure-key-vault",
|
||||||
|
"integrations/secret-syncs/bitbucket",
|
||||||
"integrations/secret-syncs/camunda",
|
"integrations/secret-syncs/camunda",
|
||||||
"integrations/secret-syncs/checkly",
|
"integrations/secret-syncs/checkly",
|
||||||
"integrations/secret-syncs/cloudflare-pages",
|
"integrations/secret-syncs/cloudflare-pages",
|
||||||
@@ -1707,6 +1708,20 @@
|
|||||||
"api-reference/endpoints/secret-syncs/azure-key-vault/remove-secrets"
|
"api-reference/endpoints/secret-syncs/azure-key-vault/remove-secrets"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"group": "Bitbucket",
|
||||||
|
"pages": [
|
||||||
|
"api-reference/endpoints/secret-syncs/bitbucket/list",
|
||||||
|
"api-reference/endpoints/secret-syncs/bitbucket/get-by-id",
|
||||||
|
"api-reference/endpoints/secret-syncs/bitbucket/get-by-name",
|
||||||
|
"api-reference/endpoints/secret-syncs/bitbucket/create",
|
||||||
|
"api-reference/endpoints/secret-syncs/bitbucket/update",
|
||||||
|
"api-reference/endpoints/secret-syncs/bitbucket/delete",
|
||||||
|
"api-reference/endpoints/secret-syncs/bitbucket/sync-secrets",
|
||||||
|
"api-reference/endpoints/secret-syncs/bitbucket/import-secrets",
|
||||||
|
"api-reference/endpoints/secret-syncs/bitbucket/remove-secrets"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"group": "Camunda",
|
"group": "Camunda",
|
||||||
"pages": [
|
"pages": [
|
||||||
|
|||||||
|
After Width: | Height: | Size: 563 KiB |
|
After Width: | Height: | Size: 541 KiB |
|
After Width: | Height: | Size: 532 KiB |
|
After Width: | Height: | Size: 580 KiB |
|
After Width: | Height: | Size: 581 KiB |
|
After Width: | Height: | Size: 613 KiB |
|
After Width: | Height: | Size: 927 KiB |
@@ -0,0 +1,158 @@
|
|||||||
|
---
|
||||||
|
title: "Bitbucket Sync"
|
||||||
|
description: "Learn how to configure a Bitbucket Sync for Infisical."
|
||||||
|
---
|
||||||
|
|
||||||
|
**Prerequisites:**
|
||||||
|
- Create a [Bitbucket Connection](/integrations/app-connections/bitbucket)
|
||||||
|
|
||||||
|
<Tabs>
|
||||||
|
<Tab title="Infisical UI">
|
||||||
|
<Steps>
|
||||||
|
<Step title="Add Sync">
|
||||||
|
Navigate to **Project** > **Integrations** and select the **Secret Syncs** tab. Click on the **Add Sync** button.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Select 'Bitbucket'">
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Configure source">
|
||||||
|
Configure the **Source** from where secrets should be retrieved, then click **Next**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **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>
|
||||||
|
</Step>
|
||||||
|
<Step title="Configure destination">
|
||||||
|
Configure the **Destination** to where secrets should be deployed, then click **Next**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **Bitbucket Connection**: The Bitbucket Connection to authenticate with.
|
||||||
|
- **Workspace**: The Bitbucket workspace to sync secrets to.
|
||||||
|
- **Repository**: The Bitbucket repository to sync secrets to.
|
||||||
|
- **Deployment Environment (Optional)**: The Bitbucket deployment environment to sync secrets to.
|
||||||
|
</Step>
|
||||||
|
<Step title="Configure sync options">
|
||||||
|
Configure the **Sync Options** to specify how secrets should be synced, then click **Next**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **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.
|
||||||
|
- **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over Bitbucket when keys conflict.
|
||||||
|
- **Import Secrets (Prioritize Bitbucket)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Bitbucket over Infisical when keys conflict.
|
||||||
|
- **Key Schema**: Template that determines how secret names are transformed when syncing, using `{{secretKey}}` as a placeholder for the original secret name and `{{environment}}` for the environment.
|
||||||
|
<Note>
|
||||||
|
We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched.
|
||||||
|
</Note>
|
||||||
|
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
|
||||||
|
- **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical.
|
||||||
|
</Step>
|
||||||
|
<Step title="Configure details">
|
||||||
|
Configure the **Details** of your Bitbucket Sync, then click **Next**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- **Name**: The name of your sync. Must be slug-friendly.
|
||||||
|
- **Description**: An optional description for your sync.
|
||||||
|
</Step>
|
||||||
|
<Step title="Review configuration">
|
||||||
|
Review your Bitbucket Sync configuration, then click **Create Sync**.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Sync created">
|
||||||
|
If enabled, your Bitbucket Sync will begin syncing your secrets to the destination endpoint.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
</Tab>
|
||||||
|
<Tab title="API">
|
||||||
|
To create a **Bitbucket Sync**, make an API request to the [Create Bitbucket Sync](/api-reference/endpoints/secret-syncs/bitbucket/create) API endpoint.
|
||||||
|
|
||||||
|
### Sample request
|
||||||
|
|
||||||
|
```bash Request
|
||||||
|
curl --request POST \
|
||||||
|
--url https://app.infisical.com/api/v1/secret-syncs/bitbucket \
|
||||||
|
--header 'Content-Type: application/json' \
|
||||||
|
--data '{
|
||||||
|
"name": "my-bitbucket-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": {
|
||||||
|
"workspace": "...",
|
||||||
|
"repository": "..."
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sample response
|
||||||
|
|
||||||
|
```bash Response
|
||||||
|
{
|
||||||
|
"secretSync": {
|
||||||
|
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
|
"name": "my-bitbucket-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": "bitbucket",
|
||||||
|
"name": "my-bitbucket-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": "bitbucket",
|
||||||
|
"destinationConfig": {
|
||||||
|
"workspace": "...",
|
||||||
|
"repository": "..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
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 {
|
||||||
|
TBitbucketEnvironment,
|
||||||
|
TBitbucketRepo,
|
||||||
|
TBitbucketWorkspace,
|
||||||
|
useBitbucketConnectionListEnvironments,
|
||||||
|
useBitbucketConnectionListRepositories,
|
||||||
|
useBitbucketConnectionListWorkspaces
|
||||||
|
} from "@app/hooks/api/appConnections/bitbucket";
|
||||||
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
|
||||||
|
import { TSecretSyncForm } from "../schemas";
|
||||||
|
|
||||||
|
export const BitbucketSyncFields = () => {
|
||||||
|
const { control, setValue } = useFormContext<
|
||||||
|
TSecretSyncForm & { destination: SecretSync.Bitbucket }
|
||||||
|
>();
|
||||||
|
|
||||||
|
const connectionId = useWatch({ name: "connection.id", control });
|
||||||
|
const workspace = useWatch({ name: "destinationConfig.workspace", control });
|
||||||
|
const repository = useWatch({ name: "destinationConfig.repository", control });
|
||||||
|
|
||||||
|
const { data: workspaces = [], isPending: isWorkspacesLoading } =
|
||||||
|
useBitbucketConnectionListWorkspaces(connectionId, {
|
||||||
|
enabled: Boolean(connectionId)
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: repositories = [], isPending: isRepositoriesLoading } =
|
||||||
|
useBitbucketConnectionListRepositories(connectionId, workspace ?? "", {
|
||||||
|
enabled: Boolean(connectionId) && Boolean(workspace)
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: environments = [], isPending: isEnvironmentsLoading } =
|
||||||
|
useBitbucketConnectionListEnvironments(connectionId, workspace ?? "", repository ?? "", {
|
||||||
|
enabled: Boolean(connectionId) && Boolean(workspace) && Boolean(repository)
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SecretSyncConnectionField
|
||||||
|
onChange={() => {
|
||||||
|
setValue("destinationConfig.workspace", "");
|
||||||
|
setValue("destinationConfig.repository", "");
|
||||||
|
setValue("destinationConfig.environment", "");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
name="destinationConfig.workspace"
|
||||||
|
control={control}
|
||||||
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
label="Bitbucket Workspace"
|
||||||
|
tooltipClassName="max-w-md"
|
||||||
|
>
|
||||||
|
<FilterableSelect
|
||||||
|
isLoading={isWorkspacesLoading && Boolean(connectionId)}
|
||||||
|
isDisabled={!connectionId}
|
||||||
|
value={workspaces.find((w) => w.slug === value) ?? null}
|
||||||
|
onChange={(option) => {
|
||||||
|
const v = option as SingleValue<TBitbucketWorkspace>;
|
||||||
|
onChange(v?.slug ?? "");
|
||||||
|
// Clear downstream selections
|
||||||
|
setValue("destinationConfig.repository", "");
|
||||||
|
setValue("destinationConfig.environment", "");
|
||||||
|
}}
|
||||||
|
options={workspaces}
|
||||||
|
placeholder="Select workspace..."
|
||||||
|
getOptionLabel={(option) => option.slug}
|
||||||
|
getOptionValue={(option) => option.slug}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
name="destinationConfig.repository"
|
||||||
|
control={control}
|
||||||
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
label="Bitbucket Repository"
|
||||||
|
tooltipClassName="max-w-md"
|
||||||
|
>
|
||||||
|
<FilterableSelect
|
||||||
|
isLoading={isRepositoriesLoading && Boolean(workspace)}
|
||||||
|
isDisabled={!workspace}
|
||||||
|
value={repositories.find((r) => r.slug === value) ?? null}
|
||||||
|
onChange={(option) => {
|
||||||
|
const v = option as SingleValue<TBitbucketRepo>;
|
||||||
|
onChange(v?.slug ?? "");
|
||||||
|
// Clear downstream selections
|
||||||
|
setValue("destinationConfig.environment", "");
|
||||||
|
}}
|
||||||
|
options={repositories}
|
||||||
|
placeholder="Select repository..."
|
||||||
|
getOptionLabel={(option) => option.full_name}
|
||||||
|
getOptionValue={(option) => option.slug}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
name="destinationConfig.environment"
|
||||||
|
control={control}
|
||||||
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
label="Bitbucket Deployment Environment (optional)"
|
||||||
|
tooltipClassName="max-w-md"
|
||||||
|
>
|
||||||
|
<FilterableSelect
|
||||||
|
isLoading={isEnvironmentsLoading && Boolean(repository)}
|
||||||
|
isDisabled={!repository}
|
||||||
|
value={environments.find((e) => e.uuid === value) ?? null}
|
||||||
|
onChange={(option) => {
|
||||||
|
const v = option as SingleValue<TBitbucketEnvironment>;
|
||||||
|
onChange(v?.uuid ?? "");
|
||||||
|
}}
|
||||||
|
options={environments}
|
||||||
|
placeholder="Select environment..."
|
||||||
|
getOptionLabel={(option) => option.name}
|
||||||
|
getOptionValue={(option) => option.uuid}
|
||||||
|
isClearable
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -9,6 +9,7 @@ import { AwsSecretsManagerSyncFields } from "./AwsSecretsManagerSyncFields";
|
|||||||
import { AzureAppConfigurationSyncFields } from "./AzureAppConfigurationSyncFields";
|
import { AzureAppConfigurationSyncFields } from "./AzureAppConfigurationSyncFields";
|
||||||
import { AzureDevOpsSyncFields } from "./AzureDevOpsSyncFields";
|
import { AzureDevOpsSyncFields } from "./AzureDevOpsSyncFields";
|
||||||
import { AzureKeyVaultSyncFields } from "./AzureKeyVaultSyncFields";
|
import { AzureKeyVaultSyncFields } from "./AzureKeyVaultSyncFields";
|
||||||
|
import { BitbucketSyncFields } from "./BitbucketSyncFields";
|
||||||
import { CamundaSyncFields } from "./CamundaSyncFields";
|
import { CamundaSyncFields } from "./CamundaSyncFields";
|
||||||
import { ChecklySyncFields } from "./ChecklySyncFields";
|
import { ChecklySyncFields } from "./ChecklySyncFields";
|
||||||
import { CloudflarePagesSyncFields } from "./CloudflarePagesSyncFields";
|
import { CloudflarePagesSyncFields } from "./CloudflarePagesSyncFields";
|
||||||
@@ -91,6 +92,8 @@ export const SecretSyncDestinationFields = () => {
|
|||||||
return <ChecklySyncFields />;
|
return <ChecklySyncFields />;
|
||||||
case SecretSync.Supabase:
|
case SecretSync.Supabase:
|
||||||
return <SupabaseSyncFields />;
|
return <SupabaseSyncFields />;
|
||||||
|
case SecretSync.Bitbucket:
|
||||||
|
return <BitbucketSyncFields />;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled Destination Config Field: ${destination}`);
|
throw new Error(`Unhandled Destination Config Field: ${destination}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
|
|||||||
case SecretSync.Railway:
|
case SecretSync.Railway:
|
||||||
case SecretSync.Checkly:
|
case SecretSync.Checkly:
|
||||||
case SecretSync.Supabase:
|
case SecretSync.Supabase:
|
||||||
|
case SecretSync.Bitbucket:
|
||||||
AdditionalSyncOptionsFieldsComponent = null;
|
AdditionalSyncOptionsFieldsComponent = null;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { useFormContext } from "react-hook-form";
|
||||||
|
|
||||||
|
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
|
||||||
|
import { GenericFieldLabel } from "@app/components/v2";
|
||||||
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
|
||||||
|
export const BitBucketSyncReviewFields = () => {
|
||||||
|
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.Bitbucket }>();
|
||||||
|
const repository = watch("destinationConfig.repository");
|
||||||
|
const environment = watch("destinationConfig.environment");
|
||||||
|
const workspace = watch("destinationConfig.workspace");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<GenericFieldLabel label="Repository">{repository}</GenericFieldLabel>
|
||||||
|
<GenericFieldLabel label="Environment">{environment}</GenericFieldLabel>
|
||||||
|
<GenericFieldLabel label="Workspace">{workspace}</GenericFieldLabel>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
import { AzureAppConfigurationSyncReviewFields } from "./AzureAppConfigurationSyncReviewFields";
|
import { AzureAppConfigurationSyncReviewFields } from "./AzureAppConfigurationSyncReviewFields";
|
||||||
import { AzureDevOpsSyncReviewFields } from "./AzureDevOpsSyncReviewFields";
|
import { AzureDevOpsSyncReviewFields } from "./AzureDevOpsSyncReviewFields";
|
||||||
import { AzureKeyVaultSyncReviewFields } from "./AzureKeyVaultSyncReviewFields";
|
import { AzureKeyVaultSyncReviewFields } from "./AzureKeyVaultSyncReviewFields";
|
||||||
|
import { BitBucketSyncReviewFields } from "./BitBucketSyncReviewFields";
|
||||||
import { CamundaSyncReviewFields } from "./CamundaSyncReviewFields";
|
import { CamundaSyncReviewFields } from "./CamundaSyncReviewFields";
|
||||||
import { ChecklySyncReviewFields } from "./ChecklySyncReviewFields";
|
import { ChecklySyncReviewFields } from "./ChecklySyncReviewFields";
|
||||||
import { CloudflarePagesSyncReviewFields } from "./CloudflarePagesReviewFields";
|
import { CloudflarePagesSyncReviewFields } from "./CloudflarePagesReviewFields";
|
||||||
@@ -144,6 +145,9 @@ export const SecretSyncReviewFields = () => {
|
|||||||
case SecretSync.Supabase:
|
case SecretSync.Supabase:
|
||||||
DestinationFieldsComponent = <SupabaseSyncReviewFields />;
|
DestinationFieldsComponent = <SupabaseSyncReviewFields />;
|
||||||
break;
|
break;
|
||||||
|
case SecretSync.Bitbucket:
|
||||||
|
DestinationFieldsComponent = <BitBucketSyncReviewFields />;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled Destination Review Fields: ${destination}`);
|
throw new Error(`Unhandled Destination Review Fields: ${destination}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { BaseSecretSyncSchema } from "@app/components/secret-syncs/forms/schemas/base-secret-sync-schema";
|
||||||
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
|
||||||
|
export const BitbucketSyncDestinationSchema = BaseSecretSyncSchema().merge(
|
||||||
|
z.object({
|
||||||
|
destination: z.literal(SecretSync.Bitbucket),
|
||||||
|
destinationConfig: z.object({
|
||||||
|
repository: z.string().trim().describe("Repository Name"),
|
||||||
|
environment: z.string().trim().optional().describe("Environment Name"),
|
||||||
|
workspace: z.string().trim().describe("Workspace Name")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
);
|
||||||
@@ -6,6 +6,7 @@ import { AwsSecretsManagerSyncDestinationSchema } from "./aws-secrets-manager-sy
|
|||||||
import { AzureAppConfigurationSyncDestinationSchema } from "./azure-app-configuration-sync-destination-schema";
|
import { AzureAppConfigurationSyncDestinationSchema } from "./azure-app-configuration-sync-destination-schema";
|
||||||
import { AzureDevOpsSyncDestinationSchema } from "./azure-devops-sync-destination-schema";
|
import { AzureDevOpsSyncDestinationSchema } from "./azure-devops-sync-destination-schema";
|
||||||
import { AzureKeyVaultSyncDestinationSchema } from "./azure-key-vault-sync-destination-schema";
|
import { AzureKeyVaultSyncDestinationSchema } from "./azure-key-vault-sync-destination-schema";
|
||||||
|
import { BitbucketSyncDestinationSchema } from "./bitbucket-sync-destination-schema";
|
||||||
import { CamundaSyncDestinationSchema } from "./camunda-sync-destination-schema";
|
import { CamundaSyncDestinationSchema } from "./camunda-sync-destination-schema";
|
||||||
import { ChecklySyncDestinationSchema } from "./checkly-sync-destination-schema";
|
import { ChecklySyncDestinationSchema } from "./checkly-sync-destination-schema";
|
||||||
import { CloudflarePagesSyncDestinationSchema } from "./cloudflare-pages-sync-destination-schema";
|
import { CloudflarePagesSyncDestinationSchema } from "./cloudflare-pages-sync-destination-schema";
|
||||||
@@ -55,7 +56,8 @@ const SecretSyncUnionSchema = z.discriminatedUnion("destination", [
|
|||||||
SupabaseSyncDestinationSchema,
|
SupabaseSyncDestinationSchema,
|
||||||
ZabbixSyncDestinationSchema,
|
ZabbixSyncDestinationSchema,
|
||||||
RailwaySyncDestinationSchema,
|
RailwaySyncDestinationSchema,
|
||||||
ChecklySyncDestinationSchema
|
ChecklySyncDestinationSchema,
|
||||||
|
BitbucketSyncDestinationSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const SecretSyncFormSchema = SecretSyncUnionSchema;
|
export const SecretSyncFormSchema = SecretSyncUnionSchema;
|
||||||
|
|||||||
@@ -101,6 +101,10 @@ export const SECRET_SYNC_MAP: Record<SecretSync, { name: string; image: string }
|
|||||||
[SecretSync.Supabase]: {
|
[SecretSync.Supabase]: {
|
||||||
name: "Supabase",
|
name: "Supabase",
|
||||||
image: "Supabase.png"
|
image: "Supabase.png"
|
||||||
|
},
|
||||||
|
[SecretSync.Bitbucket]: {
|
||||||
|
name: "Bitbucket",
|
||||||
|
image: "Bitbucket.png"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -131,7 +135,8 @@ export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
|||||||
[SecretSync.Supabase]: AppConnection.Supabase,
|
[SecretSync.Supabase]: AppConnection.Supabase,
|
||||||
[SecretSync.Zabbix]: AppConnection.Zabbix,
|
[SecretSync.Zabbix]: AppConnection.Zabbix,
|
||||||
[SecretSync.Railway]: AppConnection.Railway,
|
[SecretSync.Railway]: AppConnection.Railway,
|
||||||
[SecretSync.Checkly]: AppConnection.Checkly
|
[SecretSync.Checkly]: AppConnection.Checkly,
|
||||||
|
[SecretSync.Bitbucket]: AppConnection.Bitbucket
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP: Record<
|
export const SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP: Record<
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import { apiRequest } from "@app/config/request";
|
|||||||
|
|
||||||
import { appConnectionKeys } from "../queries";
|
import { appConnectionKeys } from "../queries";
|
||||||
import {
|
import {
|
||||||
|
TBitbucketConnectionListEnvironmentsResponse,
|
||||||
TBitbucketConnectionListRepositoriesResponse,
|
TBitbucketConnectionListRepositoriesResponse,
|
||||||
TBitbucketConnectionListWorkspacesResponse,
|
TBitbucketConnectionListWorkspacesResponse,
|
||||||
|
TBitbucketEnvironment,
|
||||||
TBitbucketRepo,
|
TBitbucketRepo,
|
||||||
TBitbucketWorkspace
|
TBitbucketWorkspace
|
||||||
} from "./types";
|
} from "./types";
|
||||||
@@ -15,7 +17,9 @@ const bitbucketConnectionKeys = {
|
|||||||
listRepos: (connectionId: string, workspaceSlug: string) =>
|
listRepos: (connectionId: string, workspaceSlug: string) =>
|
||||||
[...bitbucketConnectionKeys.all, "repos", connectionId, workspaceSlug] as const,
|
[...bitbucketConnectionKeys.all, "repos", connectionId, workspaceSlug] as const,
|
||||||
listWorkspaces: (connectionId: string) =>
|
listWorkspaces: (connectionId: string) =>
|
||||||
[...bitbucketConnectionKeys.all, "workspaces", connectionId] as const
|
[...bitbucketConnectionKeys.all, "workspaces", connectionId] as const,
|
||||||
|
listEnvironments: (connectionId: string, workspaceSlug: string, repoSlug: string) =>
|
||||||
|
[...bitbucketConnectionKeys.all, "environments", connectionId, workspaceSlug, repoSlug] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useBitbucketConnectionListWorkspaces = (
|
export const useBitbucketConnectionListWorkspaces = (
|
||||||
@@ -68,3 +72,30 @@ export const useBitbucketConnectionListRepositories = (
|
|||||||
...options
|
...options
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useBitbucketConnectionListEnvironments = (
|
||||||
|
connectionId: string,
|
||||||
|
workspaceSlug: string,
|
||||||
|
repoSlug: string,
|
||||||
|
options?: Omit<
|
||||||
|
UseQueryOptions<
|
||||||
|
TBitbucketEnvironment[],
|
||||||
|
unknown,
|
||||||
|
TBitbucketEnvironment[],
|
||||||
|
ReturnType<typeof bitbucketConnectionKeys.listEnvironments>
|
||||||
|
>,
|
||||||
|
"queryKey" | "queryFn"
|
||||||
|
>
|
||||||
|
) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: bitbucketConnectionKeys.listEnvironments(connectionId, workspaceSlug, repoSlug),
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await apiRequest.get<TBitbucketConnectionListEnvironmentsResponse>(
|
||||||
|
`/api/v1/app-connections/bitbucket/${connectionId}/environments?workspaceSlug=${encodeURIComponent(workspaceSlug)}&repositorySlug=${encodeURIComponent(repoSlug)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return data.environments;
|
||||||
|
},
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -15,3 +15,13 @@ export type TBitbucketConnectionListWorkspacesResponse = {
|
|||||||
export type TBitbucketConnectionListRepositoriesResponse = {
|
export type TBitbucketConnectionListRepositoriesResponse = {
|
||||||
repositories: TBitbucketRepo[];
|
repositories: TBitbucketRepo[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TBitbucketEnvironment = {
|
||||||
|
uuid: string;
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TBitbucketConnectionListEnvironmentsResponse = {
|
||||||
|
environments: TBitbucketEnvironment[];
|
||||||
|
};
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ export enum SecretSync {
|
|||||||
Supabase = "supabase",
|
Supabase = "supabase",
|
||||||
Zabbix = "zabbix",
|
Zabbix = "zabbix",
|
||||||
Railway = "railway",
|
Railway = "railway",
|
||||||
Checkly = "checkly"
|
Checkly = "checkly",
|
||||||
|
Bitbucket = "bitbucket"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SecretSyncStatus {
|
export enum SecretSyncStatus {
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||||
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
import { TRootSecretSync } from "@app/hooks/api/secretSyncs/types/root-sync";
|
||||||
|
|
||||||
|
export type TBitbucketSync = TRootSecretSync & {
|
||||||
|
destination: SecretSync.Bitbucket;
|
||||||
|
destinationConfig: {
|
||||||
|
workspace: string;
|
||||||
|
repository: string;
|
||||||
|
environment?: string;
|
||||||
|
};
|
||||||
|
connection: {
|
||||||
|
app: AppConnection.Bitbucket;
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -8,6 +8,7 @@ import { TAwsSecretsManagerSync } from "./aws-secrets-manager-sync";
|
|||||||
import { TAzureAppConfigurationSync } from "./azure-app-configuration-sync";
|
import { TAzureAppConfigurationSync } from "./azure-app-configuration-sync";
|
||||||
import { TAzureDevOpsSync } from "./azure-devops-sync";
|
import { TAzureDevOpsSync } from "./azure-devops-sync";
|
||||||
import { TAzureKeyVaultSync } from "./azure-key-vault-sync";
|
import { TAzureKeyVaultSync } from "./azure-key-vault-sync";
|
||||||
|
import { TBitbucketSync } from "./bitbucket-sync";
|
||||||
import { TCamundaSync } from "./camunda-sync";
|
import { TCamundaSync } from "./camunda-sync";
|
||||||
import { TChecklySync } from "./checkly-sync";
|
import { TChecklySync } from "./checkly-sync";
|
||||||
import { TCloudflarePagesSync } from "./cloudflare-pages-sync";
|
import { TCloudflarePagesSync } from "./cloudflare-pages-sync";
|
||||||
@@ -63,7 +64,8 @@ export type TSecretSync =
|
|||||||
| TZabbixSync
|
| TZabbixSync
|
||||||
| TRailwaySync
|
| TRailwaySync
|
||||||
| TChecklySync
|
| TChecklySync
|
||||||
| TSupabaseSync;
|
| TSupabaseSync
|
||||||
|
| TBitbucketSync;
|
||||||
|
|
||||||
export type TListSecretSyncs = { secretSyncs: TSecretSync[] };
|
export type TListSecretSyncs = { secretSyncs: TSecretSync[] };
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { TBitbucketSync } from "@app/hooks/api/secretSyncs/types/bitbucket-sync";
|
||||||
|
|
||||||
|
import { getSecretSyncDestinationColValues } from "../helpers";
|
||||||
|
import { SecretSyncTableCell } from "../SecretSyncTableCell";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
secretSync: TBitbucketSync;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BitbucketSyncDestinationCol = ({ secretSync }: Props) => {
|
||||||
|
const { primaryText, secondaryText } = getSecretSyncDestinationColValues(secretSync);
|
||||||
|
|
||||||
|
return <SecretSyncTableCell primaryText={primaryText} secondaryText={secondaryText} />;
|
||||||
|
};
|
||||||
@@ -6,6 +6,7 @@ import { AwsSecretsManagerSyncDestinationCol } from "./AwsSecretsManagerSyncDest
|
|||||||
import { AzureAppConfigurationDestinationSyncCol } from "./AzureAppConfigurationDestinationSyncCol";
|
import { AzureAppConfigurationDestinationSyncCol } from "./AzureAppConfigurationDestinationSyncCol";
|
||||||
import { AzureDevOpsSyncDestinationCol } from "./AzureDevOpsSyncDestinationCol";
|
import { AzureDevOpsSyncDestinationCol } from "./AzureDevOpsSyncDestinationCol";
|
||||||
import { AzureKeyVaultDestinationSyncCol } from "./AzureKeyVaultDestinationSyncCol";
|
import { AzureKeyVaultDestinationSyncCol } from "./AzureKeyVaultDestinationSyncCol";
|
||||||
|
import { BitbucketSyncDestinationCol } from "./BitbucketSyncDestinationCol";
|
||||||
import { CamundaSyncDestinationCol } from "./CamundaSyncDestinationCol";
|
import { CamundaSyncDestinationCol } from "./CamundaSyncDestinationCol";
|
||||||
import { ChecklySyncDestinationCol } from "./ChecklySyncDestinationCol";
|
import { ChecklySyncDestinationCol } from "./ChecklySyncDestinationCol";
|
||||||
import { CloudflarePagesSyncDestinationCol } from "./CloudflarePagesSyncDestinationCol";
|
import { CloudflarePagesSyncDestinationCol } from "./CloudflarePagesSyncDestinationCol";
|
||||||
@@ -88,6 +89,8 @@ export const SecretSyncDestinationCol = ({ secretSync }: Props) => {
|
|||||||
return <ChecklySyncDestinationCol secretSync={secretSync} />;
|
return <ChecklySyncDestinationCol secretSync={secretSync} />;
|
||||||
case SecretSync.Supabase:
|
case SecretSync.Supabase:
|
||||||
return <SupabaseSyncDestinationCol secretSync={secretSync} />;
|
return <SupabaseSyncDestinationCol secretSync={secretSync} />;
|
||||||
|
case SecretSync.Bitbucket:
|
||||||
|
return <BitbucketSyncDestinationCol secretSync={secretSync} />;
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unhandled Secret Sync Destination Col: ${(secretSync as TSecretSync).destination}`
|
`Unhandled Secret Sync Destination Col: ${(secretSync as TSecretSync).destination}`
|
||||||
|
|||||||
@@ -174,6 +174,10 @@ export const getSecretSyncDestinationColValues = (secretSync: TSecretSync) => {
|
|||||||
primaryText = destinationConfig.projectName;
|
primaryText = destinationConfig.projectName;
|
||||||
secondaryText = "Supabase Project";
|
secondaryText = "Supabase Project";
|
||||||
break;
|
break;
|
||||||
|
case SecretSync.Bitbucket:
|
||||||
|
primaryText = destinationConfig.workspace;
|
||||||
|
secondaryText = destinationConfig.repository;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled Destination Col Values ${destination}`);
|
throw new Error(`Unhandled Destination Col Values ${destination}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { GenericFieldLabel } from "@app/components/secret-syncs";
|
||||||
|
import { TBitbucketSync } from "@app/hooks/api/secretSyncs/types/bitbucket-sync";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
secretSync: TBitbucketSync;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BitbucketSyncDestinationSection = ({ secretSync }: Props) => {
|
||||||
|
const {
|
||||||
|
destinationConfig: { workspace, repository, environment }
|
||||||
|
} = secretSync;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<GenericFieldLabel label="Workspace">{workspace}</GenericFieldLabel>
|
||||||
|
<GenericFieldLabel label="Repository">{repository}</GenericFieldLabel>
|
||||||
|
{environment && (
|
||||||
|
<GenericFieldLabel label="Deployment Environment">{environment}</GenericFieldLabel>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -17,6 +17,7 @@ import { AwsSecretsManagerSyncDestinationSection } from "./AwsSecretsManagerSync
|
|||||||
import { AzureAppConfigurationSyncDestinationSection } from "./AzureAppConfigurationSyncDestinationSection";
|
import { AzureAppConfigurationSyncDestinationSection } from "./AzureAppConfigurationSyncDestinationSection";
|
||||||
import { AzureDevOpsSyncDestinationSection } from "./AzureDevOpsSyncDestinationSection";
|
import { AzureDevOpsSyncDestinationSection } from "./AzureDevOpsSyncDestinationSection";
|
||||||
import { AzureKeyVaultSyncDestinationSection } from "./AzureKeyVaultSyncDestinationSection";
|
import { AzureKeyVaultSyncDestinationSection } from "./AzureKeyVaultSyncDestinationSection";
|
||||||
|
import { BitbucketSyncDestinationSection } from "./BitbucketSyncDestinationSection";
|
||||||
import { CamundaSyncDestinationSection } from "./CamundaSyncDestinationSection";
|
import { CamundaSyncDestinationSection } from "./CamundaSyncDestinationSection";
|
||||||
import { ChecklySyncDestinationSection } from "./ChecklySyncDestinationSection";
|
import { ChecklySyncDestinationSection } from "./ChecklySyncDestinationSection";
|
||||||
import { CloudflarePagesSyncDestinationSection } from "./CloudflarePagesSyncDestinationSection";
|
import { CloudflarePagesSyncDestinationSection } from "./CloudflarePagesSyncDestinationSection";
|
||||||
@@ -134,6 +135,9 @@ export const SecretSyncDestinationSection = ({ secretSync, onEditDestination }:
|
|||||||
case SecretSync.Supabase:
|
case SecretSync.Supabase:
|
||||||
DestinationComponents = <SupabaseSyncDestinationSection secretSync={secretSync} />;
|
DestinationComponents = <SupabaseSyncDestinationSection secretSync={secretSync} />;
|
||||||
break;
|
break;
|
||||||
|
case SecretSync.Bitbucket:
|
||||||
|
DestinationComponents = <BitbucketSyncDestinationSection secretSync={secretSync} />;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled Destination Section components: ${destination}`);
|
throw new Error(`Unhandled Destination Section components: ${destination}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) =
|
|||||||
case SecretSync.Railway:
|
case SecretSync.Railway:
|
||||||
case SecretSync.Supabase:
|
case SecretSync.Supabase:
|
||||||
case SecretSync.Checkly:
|
case SecretSync.Checkly:
|
||||||
|
case SecretSync.Bitbucket:
|
||||||
AdditionalSyncOptionsComponent = null;
|
AdditionalSyncOptionsComponent = null;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||