Addressed PR comments

This commit is contained in:
Carlos Monastyrski
2025-07-21 18:24:48 -03:00
parent 5019918516
commit 328f929a29
16 changed files with 153 additions and 181 deletions
+3 -3
View File
@@ -2504,9 +2504,9 @@ export const SecretSyncs = {
projectName: "The name of the Supabase project to sync secrets to." projectName: "The name of the Supabase project to sync secrets to."
}, },
BITBUCKET: { BITBUCKET: {
workspace: "The Bitbucket Workspace slug to sync secrets to.", workspaceSlug: "The Bitbucket Workspace slug to sync secrets to.",
repository: "The Bitbucket Repository slug to sync secrets to.", repositorySlug: "The Bitbucket Repository slug to sync secrets to.",
environment: "The Bitbucket Deployment Environment uuid to sync secrets to." environmentId: "The Bitbucket Deployment Environment uuid to sync secrets to."
} }
} }
}; };
@@ -22,11 +22,15 @@ export const getBitbucketConnectionListItem = () => {
}; };
}; };
export const createAuthHeader = (email: string, apiToken: string): string => {
return `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`;
};
export const getBitbucketUser = async ({ email, apiToken }: { email: string; apiToken: string }) => { export const getBitbucketUser = async ({ email, apiToken }: { email: string; apiToken: string }) => {
try { try {
const { data } = await request.get<{ username: string }>(`${IntegrationUrls.BITBUCKET_API_URL}/2.0/user`, { const { data } = await request.get<{ username: string }>(`${IntegrationUrls.BITBUCKET_API_URL}/2.0/user`, {
headers: { headers: {
Authorization: `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`, Authorization: createAuthHeader(email, apiToken),
Accept: "application/json" Accept: "application/json"
} }
}); });
@@ -58,7 +62,7 @@ export const listBitbucketWorkspaces = async (appConnection: TBitbucketConnectio
const { email, apiToken } = appConnection.credentials; const { email, apiToken } = appConnection.credentials;
const headers = { const headers = {
Authorization: `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`, Authorization: createAuthHeader(email, apiToken),
Accept: "application/json" Accept: "application/json"
}; };
@@ -90,7 +94,7 @@ export const listBitbucketRepositories = async (appConnection: TBitbucketConnect
const { email, apiToken } = appConnection.credentials; const { email, apiToken } = appConnection.credentials;
const headers = { const headers = {
Authorization: `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`, Authorization: createAuthHeader(email, apiToken),
Accept: "application/json" Accept: "application/json"
}; };
@@ -125,7 +129,7 @@ export const listBitbucketEnvironments = async (
const { email, apiToken } = appConnection.credentials; const { email, apiToken } = appConnection.credentials;
const headers = { const headers = {
Authorization: `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`, Authorization: createAuthHeader(email, apiToken),
Accept: "application/json" Accept: "application/json"
}; };
@@ -134,7 +138,9 @@ export const listBitbucketEnvironments = async (
let environmentsUrl = `${IntegrationUrls.BITBUCKET_API_URL}/2.0/repositories/${encodeURIComponent(workspaceSlug)}/${encodeURIComponent(repositorySlug)}/environments?pagelen=100`; let environmentsUrl = `${IntegrationUrls.BITBUCKET_API_URL}/2.0/repositories/${encodeURIComponent(workspaceSlug)}/${encodeURIComponent(repositorySlug)}/environments?pagelen=100`;
while (hasNextPage) { let iterationCount = 0;
// Limit to 10 iterations, fetching at most 10 * 100 = 1000 environments
while (hasNextPage && iterationCount < 100) {
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
const { data }: { data: { values: TBitbucketEnvironment[]; next: string } } = await request.get(environmentsUrl, { const { data }: { data: { values: TBitbucketEnvironment[]; next: string } } = await request.get(environmentsUrl, {
headers headers
@@ -149,6 +155,7 @@ export const listBitbucketEnvironments = async (
} else { } else {
hasNextPage = false; hasNextPage = false;
} }
iterationCount += 1;
} }
return environments; return environments;
@@ -6,5 +6,5 @@ export const BITBUCKET_SYNC_LIST_OPTION: TSecretSyncListItem = {
name: "Bitbucket", name: "Bitbucket",
destination: SecretSync.Bitbucket, destination: SecretSync.Bitbucket,
connection: AppConnection.Bitbucket, connection: AppConnection.Bitbucket,
canImportSecrets: true canImportSecrets: false
}; };
@@ -1,4 +1,5 @@
import { request } from "@app/lib/config/request"; import { request } from "@app/lib/config/request";
import { createAuthHeader } from "@app/services/app-connection/bitbucket";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list"; import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { import {
TBitbucketListVariables, TBitbucketListVariables,
@@ -11,29 +12,25 @@ import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns"; import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types"; import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
const createAuthHeader = (email: string, apiToken: string): string => { import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
return `Basic ${Buffer.from(`${email}:${apiToken}`).toString("base64")}`;
};
const buildVariablesUrl = (workspace: string, repository: string, environment?: string, uuid?: string): string => { const buildVariablesUrl = (workspace: string, repository: string, environment?: string, uuid?: string): string => {
const baseUrl = `${IntegrationUrls.BITBUCKET_API_URL}/2.0/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repository)}`; const baseUrl = `${IntegrationUrls.BITBUCKET_API_URL}/2.0/repositories/${encodeURIComponent(workspace)}/${encodeURIComponent(repository)}`;
if (environment) { if (environment) {
return `${baseUrl}/deployments_config/environments/${environment}/variables${uuid ? `/${uuid}` : ""}`; return `${baseUrl}/deployments_config/environments/${environment}/variables/${uuid || ""}`;
} }
return `${baseUrl}/pipelines_config/variables/${uuid || ""}`; return `${baseUrl}/pipelines_config/variables/${uuid || ""}`;
}; };
const listVariables = async ({ const listVariables = async ({
email, workspaceSlug,
apiToken, repositorySlug,
workspace, environmentId,
repository, authHeader
environment }: TBitbucketListVariables): Promise<TBitbucketVariable[]> => {
}: TBitbucketListVariables & { environment?: string }): Promise<TBitbucketVariable[]> => { const url = buildVariablesUrl(workspaceSlug, repositorySlug, environmentId);
const url = buildVariablesUrl(workspace, repository, environment);
const authHeader = createAuthHeader(email, apiToken);
const { data } = await request.get<{ values: TBitbucketVariable[] }>(url, { const { data } = await request.get<{ values: TBitbucketVariable[] }>(url, {
headers: { headers: {
@@ -46,26 +43,23 @@ const listVariables = async ({
}; };
const upsertVariable = async ({ const upsertVariable = async ({
email, workspaceSlug,
apiToken, repositorySlug,
workspace, environmentId,
repository,
environment,
key, key,
value, value,
existingVariables existingVariables,
authHeader
}: { }: {
email: string; workspaceSlug: string;
apiToken: string; repositorySlug: string;
workspace: string; environmentId?: string;
repository: string;
environment?: string;
key: string; key: string;
value: string; value: string;
existingVariables: TBitbucketVariable[]; existingVariables: TBitbucketVariable[];
authHeader: string;
}) => { }) => {
const existingVariable = existingVariables.find((variable) => variable.key === key); const existingVariable = existingVariables.find((variable) => variable.key === key);
const authHeader = createAuthHeader(email, apiToken);
const requestData = { key, value, secured: true }; const requestData = { key, value, secured: true };
const headers = { const headers = {
Authorization: authHeader, Authorization: authHeader,
@@ -73,40 +67,37 @@ const upsertVariable = async ({
}; };
if (existingVariable) { if (existingVariable) {
const url = buildVariablesUrl(workspace, repository, environment, existingVariable.uuid); const url = buildVariablesUrl(workspaceSlug, repositorySlug, environmentId, existingVariable.uuid);
return request.put(url, requestData, { headers }); return request.put(url, requestData, { headers });
} }
const url = buildVariablesUrl(workspace, repository, environment); const url = buildVariablesUrl(workspaceSlug, repositorySlug, environmentId);
return request.post(url, requestData, { headers }); return request.post(url, requestData, { headers });
}; };
const putVariables = async ({ const putVariables = async ({
email, workspaceSlug,
apiToken, repositorySlug,
workspace, environmentId,
repository, secretMap,
environment, authHeader
secretMap }: TPutBitbucketVariable & { secretMap: TSecretMap; authHeader: string }) => {
}: TPutBitbucketVariable & { environment?: string; secretMap: TSecretMap }) => {
const existingVariables = await listVariables({ const existingVariables = await listVariables({
email, workspaceSlug,
apiToken, repositorySlug,
workspace, environmentId,
repository, authHeader
environment
}); });
const promises = Object.entries(secretMap).map(([key, { value }]) => const promises = Object.entries(secretMap).map(([key, { value }]) =>
upsertVariable({ upsertVariable({
email, workspaceSlug,
apiToken, repositorySlug,
workspace, environmentId,
repository,
environment,
key, key,
value, value,
existingVariables existingVariables,
authHeader
}) })
); );
@@ -114,26 +105,22 @@ const putVariables = async ({
}; };
const deleteVariables = async ({ const deleteVariables = async ({
email, workspaceSlug,
apiToken, repositorySlug,
workspace, environmentId,
repository, keys,
environment, authHeader
keys }: TDeleteBitbucketVariable) => {
}: TDeleteBitbucketVariable & { environment?: string }) => {
const existingVariables = await listVariables({ const existingVariables = await listVariables({
email, workspaceSlug,
apiToken, repositorySlug,
workspace, environmentId,
repository, authHeader
environment
}); });
const variablesToDelete = existingVariables.filter((variable) => keys.includes(variable.key)); const variablesToDelete = existingVariables.filter((variable) => keys.includes(variable.key));
const authHeader = createAuthHeader(email, apiToken);
const promises = variablesToDelete.map((variable) => { const promises = variablesToDelete.map((variable) => {
const url = buildVariablesUrl(workspace, repository, environment, variable.uuid); const url = buildVariablesUrl(workspaceSlug, repositorySlug, environmentId, variable.uuid);
return request.delete(url, { return request.delete(url, {
headers: { Authorization: authHeader } headers: { Authorization: authHeader }
}); });
@@ -147,19 +134,19 @@ export const BitbucketSyncFns = {
const { const {
connection, connection,
environment, environment,
destinationConfig: { workspace, repository, environment: configEnvironment } destinationConfig: { workspaceSlug, repositorySlug, environmentId }
} = secretSync; } = secretSync;
const { email, apiToken } = connection.credentials; const { email, apiToken } = connection.credentials;
const authHeader = createAuthHeader(email, apiToken);
try { try {
await putVariables({ await putVariables({
email, workspaceSlug,
apiToken, repositorySlug,
workspace, environmentId,
repository, secretMap,
environment: configEnvironment, authHeader
secretMap
}); });
} catch (error) { } catch (error) {
throw new SecretSyncError({ error }); throw new SecretSyncError({ error });
@@ -169,11 +156,10 @@ export const BitbucketSyncFns = {
try { try {
const existingVariables = await listVariables({ const existingVariables = await listVariables({
email, workspaceSlug,
apiToken, repositorySlug,
workspace, environmentId,
repository, authHeader
environment: configEnvironment
}); });
const keysToDelete = existingVariables const keysToDelete = existingVariables
@@ -185,12 +171,11 @@ export const BitbucketSyncFns = {
if (keysToDelete.length > 0) { if (keysToDelete.length > 0) {
await deleteVariables({ await deleteVariables({
email, workspaceSlug,
apiToken, repositorySlug,
workspace, environmentId,
repository, keys: keysToDelete,
environment: configEnvironment, authHeader
keys: keysToDelete
}); });
} }
} catch (error) { } catch (error) {
@@ -201,30 +186,29 @@ export const BitbucketSyncFns = {
removeSecrets: async (secretSync: TBitbucketSyncWithCredentials, secretMap: TSecretMap) => { removeSecrets: async (secretSync: TBitbucketSyncWithCredentials, secretMap: TSecretMap) => {
const { const {
connection, connection,
destinationConfig: { workspace, repository, environment: configEnvironment } destinationConfig: { workspaceSlug, repositorySlug, environmentId }
} = secretSync; } = secretSync;
const { email, apiToken } = connection.credentials; const { email, apiToken } = connection.credentials;
const authHeader = createAuthHeader(email, apiToken);
try { try {
const existingVariables = await listVariables({ const existingVariables = await listVariables({
email, workspaceSlug,
apiToken, repositorySlug,
workspace, environmentId,
repository, authHeader
environment: configEnvironment
}); });
const keysToRemove = existingVariables.map((variable) => variable.key).filter((secret) => secret in secretMap); const keysToRemove = existingVariables.map((variable) => variable.key).filter((secret) => secret in secretMap);
if (keysToRemove.length > 0) { if (keysToRemove.length > 0) {
await deleteVariables({ await deleteVariables({
email, workspaceSlug,
apiToken, repositorySlug,
workspace, environmentId,
repository, keys: keysToRemove,
environment: configEnvironment, authHeader
keys: keysToRemove
}); });
} }
} catch (error) { } catch (error) {
@@ -232,34 +216,7 @@ export const BitbucketSyncFns = {
} }
}, },
getSecrets: async (secretSync: TBitbucketSyncWithCredentials) => { getSecrets: async (secretSync: TBitbucketSyncWithCredentials): Promise<TSecretMap> => {
const { throw new Error(`${SECRET_SYNC_NAME_MAP[secretSync.destination]} does not support importing secrets.`);
connection,
destinationConfig: { workspace, repository, environment }
} = secretSync;
const { email, apiToken } = connection.credentials;
try {
const variables = await listVariables({
email,
apiToken,
workspace,
repository,
environment
});
const secretMap: TSecretMap = {};
variables.forEach((variable) => {
secretMap[variable.key] = {
value: variable.secured ? "[SECURED]" : variable.value || "",
comment: ""
};
});
return secretMap;
} catch (error) {
throw new SecretSyncError({ error });
}
} }
}; };
@@ -11,12 +11,12 @@ import {
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types"; import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
const BitbucketSyncDestinationConfigSchema = z.object({ const BitbucketSyncDestinationConfigSchema = z.object({
repository: z.string().describe(SecretSyncs.DESTINATION_CONFIG.BITBUCKET.repository), repositorySlug: z.string().describe(SecretSyncs.DESTINATION_CONFIG.BITBUCKET.repositorySlug),
environment: z.string().optional().describe(SecretSyncs.DESTINATION_CONFIG.BITBUCKET.environment), environmentId: z.string().optional().describe(SecretSyncs.DESTINATION_CONFIG.BITBUCKET.environmentId),
workspace: z.string().describe(SecretSyncs.DESTINATION_CONFIG.BITBUCKET.workspace) workspaceSlug: z.string().describe(SecretSyncs.DESTINATION_CONFIG.BITBUCKET.workspaceSlug)
}); });
const BitbucketSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true }; const BitbucketSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false };
export const BitbucketSyncSchema = BaseSecretSyncSchema(SecretSync.Bitbucket, BitbucketSyncOptionsConfig).extend({ export const BitbucketSyncSchema = BaseSecretSyncSchema(SecretSync.Bitbucket, BitbucketSyncOptionsConfig).extend({
destination: z.literal(SecretSync.Bitbucket), destination: z.literal(SecretSync.Bitbucket),
@@ -41,5 +41,5 @@ export const BitbucketSyncListItemSchema = z.object({
name: z.literal("Bitbucket"), name: z.literal("Bitbucket"),
connection: z.literal(AppConnection.Bitbucket), connection: z.literal(AppConnection.Bitbucket),
destination: z.literal(SecretSync.Bitbucket), destination: z.literal(SecretSync.Bitbucket),
canImportSecrets: z.literal(true) canImportSecrets: z.literal(false)
}); });
@@ -17,34 +17,34 @@ export type TBitbucketSyncWithCredentials = TBitbucketSync & {
export type TBitbucketVariable = { export type TBitbucketVariable = {
key: string; key: string;
value?: string; value?: string;
// Secure variables values are not returned by the API neither are they shown in Bitbucket UI
secured: boolean; secured: boolean;
uuid: string; uuid: string;
type: string; type: string;
}; };
export type TBitbucketListVariables = { export type TBitbucketListVariables = {
apiToken: string; workspaceSlug: string;
email: string; repositorySlug: string;
workspace: string; environmentId?: string;
repository: string; authHeader: string;
}; };
export type TPutBitbucketVariable = { export type TPutBitbucketVariable = {
email: string; authHeader: string;
apiToken: string; workspaceSlug: string;
workspace: string; repositorySlug: string;
repository: string; environmentId?: string;
}; };
export type TDeleteBitbucketVariable = { export type TDeleteBitbucketVariable = {
email: string; authHeader: string;
apiToken: string; workspaceSlug: string;
workspace: string; repositorySlug: string;
repository: string; environmentId?: string;
keys: string[]; keys: string[];
}; };
export type TBitbucketConnectionCredentials = { export type TBitbucketConnectionCredentials = {
email: string; authHeader: string;
apiToken: string;
}; };
Binary file not shown.

After

Width:  |  Height:  |  Size: 577 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 580 KiB

After

Width:  |  Height:  |  Size: 598 KiB

@@ -50,13 +50,15 @@ Infisical supports the use of [API Tokens](https://support.atlassian.com/bitbuck
<Tab title="Secret Sync"> <Tab title="Secret Sync">
``` ```
read:workspace:bitbucket read:workspace:bitbucket
admin:workspace:bitbucket
read:user:bitbucket read:user:bitbucket
read:repository:bitbucket read:repository:bitbucket
read:pipeline:bitbucket read:pipeline:bitbucket
write:pipeline:bitbucket write:pipeline:bitbucket
admin:workspace:bitbucket
admin:pipeline:bitbucket admin:pipeline:bitbucket
``` ```
![Configure Permissions](/images/app-connections/bitbucket/step-4-secret-sync.png)
</Tab> </Tab>
</Tabs> </Tabs>
+7 -6
View File
@@ -46,8 +46,9 @@ description: "Learn how to configure a Bitbucket Sync for Infisical."
- **Initial Sync Behavior**: Determines how Infisical should resolve the initial sync. - **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. - **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. <Note>
- **Import Secrets (Prioritize Bitbucket)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Bitbucket over Infisical when keys conflict. Bitbucket does not support importing secrets.
</Note>
- **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. - **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> <Note>
We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched. We highly recommend using a Key Schema to ensure that Infisical only manages the specific keys you intend, keeping everything else untouched.
@@ -96,8 +97,8 @@ description: "Learn how to configure a Bitbucket Sync for Infisical."
"initialSyncBehavior": "overwrite-destination" "initialSyncBehavior": "overwrite-destination"
}, },
"destinationConfig": { "destinationConfig": {
"workspace": "my-bitbucket-workspace", "workspaceSlug": "my-bitbucket-workspace",
"repository": "my-bitbucket-repository" "repositorySlug": "my-bitbucket-repository"
} }
}' }'
``` ```
@@ -148,8 +149,8 @@ description: "Learn how to configure a Bitbucket Sync for Infisical."
}, },
"destination": "bitbucket", "destination": "bitbucket",
"destinationConfig": { "destinationConfig": {
"workspace": "my-bitbucket-workspace", "workspaceSlug": "my-bitbucket-workspace",
"repository": "my-bitbucket-repository" "repositorySlug": "my-bitbucket-repository"
} }
} }
} }
@@ -21,8 +21,8 @@ export const BitbucketSyncFields = () => {
>(); >();
const connectionId = useWatch({ name: "connection.id", control }); const connectionId = useWatch({ name: "connection.id", control });
const workspace = useWatch({ name: "destinationConfig.workspace", control }); const workspace = useWatch({ name: "destinationConfig.workspaceSlug", control });
const repository = useWatch({ name: "destinationConfig.repository", control }); const repository = useWatch({ name: "destinationConfig.repositorySlug", control });
const { data: workspaces = [], isPending: isWorkspacesLoading } = const { data: workspaces = [], isPending: isWorkspacesLoading } =
useBitbucketConnectionListWorkspaces(connectionId, { useBitbucketConnectionListWorkspaces(connectionId, {
@@ -43,14 +43,14 @@ export const BitbucketSyncFields = () => {
<> <>
<SecretSyncConnectionField <SecretSyncConnectionField
onChange={() => { onChange={() => {
setValue("destinationConfig.workspace", ""); setValue("destinationConfig.workspaceSlug", "");
setValue("destinationConfig.repository", ""); setValue("destinationConfig.repositorySlug", "");
setValue("destinationConfig.environment", ""); setValue("destinationConfig.environmentId", "");
}} }}
/> />
<Controller <Controller
name="destinationConfig.workspace" name="destinationConfig.workspaceSlug"
control={control} control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => ( render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl <FormControl
@@ -67,8 +67,8 @@ export const BitbucketSyncFields = () => {
const v = option as SingleValue<TBitbucketWorkspace>; const v = option as SingleValue<TBitbucketWorkspace>;
onChange(v?.slug ?? ""); onChange(v?.slug ?? "");
// Clear downstream selections // Clear downstream selections
setValue("destinationConfig.repository", ""); setValue("destinationConfig.repositorySlug", "");
setValue("destinationConfig.environment", ""); setValue("destinationConfig.environmentId", "");
}} }}
options={workspaces} options={workspaces}
placeholder="Select workspace..." placeholder="Select workspace..."
@@ -80,7 +80,7 @@ export const BitbucketSyncFields = () => {
/> />
<Controller <Controller
name="destinationConfig.repository" name="destinationConfig.repositorySlug"
control={control} control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => ( render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl <FormControl
@@ -97,7 +97,7 @@ export const BitbucketSyncFields = () => {
const v = option as SingleValue<TBitbucketRepo>; const v = option as SingleValue<TBitbucketRepo>;
onChange(v?.slug ?? ""); onChange(v?.slug ?? "");
// Clear downstream selections // Clear downstream selections
setValue("destinationConfig.environment", ""); setValue("destinationConfig.environmentId", "");
}} }}
options={repositories} options={repositories}
placeholder="Select repository..." placeholder="Select repository..."
@@ -109,13 +109,14 @@ export const BitbucketSyncFields = () => {
/> />
<Controller <Controller
name="destinationConfig.environment" name="destinationConfig.environmentId"
control={control} control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => ( render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl <FormControl
isError={Boolean(error)} isError={Boolean(error)}
errorText={error?.message} errorText={error?.message}
label="Bitbucket Deployment Environment (optional)" isOptional
label="Bitbucket Deployment Environment"
tooltipClassName="max-w-md" tooltipClassName="max-w-md"
> >
<FilterableSelect <FilterableSelect
@@ -6,9 +6,9 @@ import { SecretSync } from "@app/hooks/api/secretSyncs";
export const BitbucketSyncReviewFields = () => { export const BitbucketSyncReviewFields = () => {
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.Bitbucket }>(); const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.Bitbucket }>();
const repository = watch("destinationConfig.repository"); const repository = watch("destinationConfig.repositorySlug");
const environment = watch("destinationConfig.environment"); const environment = watch("destinationConfig.environmentId");
const workspace = watch("destinationConfig.workspace"); const workspace = watch("destinationConfig.workspaceSlug");
return ( return (
<> <>
@@ -7,9 +7,13 @@ export const BitbucketSyncDestinationSchema = BaseSecretSyncSchema().merge(
z.object({ z.object({
destination: z.literal(SecretSync.Bitbucket), destination: z.literal(SecretSync.Bitbucket),
destinationConfig: z.object({ destinationConfig: z.object({
repository: z.string().trim().min(1, "Repository slug required").describe("Repository slug"), repositorySlug: z
environment: z.string().trim().optional().describe("Deployment environment uuid"), .string()
workspace: z.string().trim().min(1, "Workspace slug required").describe("Workspace slug") .trim()
.min(1, "Repository slug required")
.describe("Repository slug"),
environmentId: z.string().trim().optional().describe("Deployment environment uuid"),
workspaceSlug: z.string().trim().min(1, "Workspace slug required").describe("Workspace slug")
}) })
}) })
); );
@@ -5,9 +5,9 @@ import { TRootSecretSync } from "@app/hooks/api/secretSyncs/types/root-sync";
export type TBitbucketSync = TRootSecretSync & { export type TBitbucketSync = TRootSecretSync & {
destination: SecretSync.Bitbucket; destination: SecretSync.Bitbucket;
destinationConfig: { destinationConfig: {
workspace: string; workspaceSlug: string;
repository: string; repositorySlug: string;
environment?: string; environmentId?: string;
}; };
connection: { connection: {
app: AppConnection.Bitbucket; app: AppConnection.Bitbucket;
@@ -175,8 +175,8 @@ export const getSecretSyncDestinationColValues = (secretSync: TSecretSync) => {
secondaryText = "Supabase Project"; secondaryText = "Supabase Project";
break; break;
case SecretSync.Bitbucket: case SecretSync.Bitbucket:
primaryText = destinationConfig.workspace; primaryText = destinationConfig.workspaceSlug;
secondaryText = destinationConfig.repository; secondaryText = destinationConfig.repositorySlug;
break; break;
default: default:
throw new Error(`Unhandled Destination Col Values ${destination}`); throw new Error(`Unhandled Destination Col Values ${destination}`);
@@ -7,15 +7,15 @@ type Props = {
export const BitbucketSyncDestinationSection = ({ secretSync }: Props) => { export const BitbucketSyncDestinationSection = ({ secretSync }: Props) => {
const { const {
destinationConfig: { workspace, repository, environment } destinationConfig: { workspaceSlug, repositorySlug, environmentId }
} = secretSync; } = secretSync;
return ( return (
<> <>
<GenericFieldLabel label="Workspace">{workspace}</GenericFieldLabel> <GenericFieldLabel label="Workspace">{workspaceSlug}</GenericFieldLabel>
<GenericFieldLabel label="Repository">{repository}</GenericFieldLabel> <GenericFieldLabel label="Repository">{repositorySlug}</GenericFieldLabel>
{environment && ( {environmentId && (
<GenericFieldLabel label="Deployment Environment">{environment}</GenericFieldLabel> <GenericFieldLabel label="Deployment Environment">{environmentId}</GenericFieldLabel>
)} )}
</> </>
); );