mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 06:28:11 +00:00
improvements: final adjustments/improvements
This commit is contained in:
@@ -1679,6 +1679,9 @@ export const SecretSyncs = {
|
|||||||
const destinationName = SECRET_SYNC_NAME_MAP[destination];
|
const destinationName = SECRET_SYNC_NAME_MAP[destination];
|
||||||
return {
|
return {
|
||||||
syncId: `The ID of the ${destinationName} Sync to be updated.`,
|
syncId: `The ID of the ${destinationName} Sync to be updated.`,
|
||||||
|
connectionId: `The updated ID of the ${
|
||||||
|
APP_CONNECTION_NAME_MAP[SECRET_SYNC_CONNECTION_MAP[destination]]
|
||||||
|
} Connection to use for syncing.`,
|
||||||
name: `The updated name of the ${destinationName} Sync. Must be slug-friendly.`,
|
name: `The updated name of the ${destinationName} Sync. Must be slug-friendly.`,
|
||||||
environment: `The updated slug of the project environment to sync secrets from.`,
|
environment: `The updated slug of the project environment to sync secrets from.`,
|
||||||
secretPath: `The updated folder path to sync secrets from.`,
|
secretPath: `The updated folder path to sync secrets from.`,
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export const registerSyncSecretsEndpoints = <T extends TSecretSync, I extends TS
|
|||||||
isEnabled?: boolean;
|
isEnabled?: boolean;
|
||||||
}>;
|
}>;
|
||||||
updateSchema: z.ZodType<{
|
updateSchema: z.ZodType<{
|
||||||
|
connectionId?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
environment?: string;
|
environment?: string;
|
||||||
secretPath?: string;
|
secretPath?: string;
|
||||||
|
|||||||
+5
-5
@@ -129,7 +129,7 @@ const deleteParametersBatch = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const AwsParameterStoreSyncFns = {
|
export const AwsParameterStoreSyncFns = {
|
||||||
syncSecrets: async (secretSync: TAwsParameterStoreSyncWithCredentials, affixedSecretMap: TSecretMap) => {
|
syncSecrets: async (secretSync: TAwsParameterStoreSyncWithCredentials, secretMap: TSecretMap) => {
|
||||||
const { destinationConfig } = secretSync;
|
const { destinationConfig } = secretSync;
|
||||||
|
|
||||||
const ssm = await getSSM(secretSync);
|
const ssm = await getSSM(secretSync);
|
||||||
@@ -138,7 +138,7 @@ export const AwsParameterStoreSyncFns = {
|
|||||||
|
|
||||||
const awsParameterStoreSecretsRecord = await getParametersByPath(ssm, destinationConfig.path);
|
const awsParameterStoreSecretsRecord = await getParametersByPath(ssm, destinationConfig.path);
|
||||||
|
|
||||||
for await (const entry of Object.entries(affixedSecretMap)) {
|
for await (const entry of Object.entries(secretMap)) {
|
||||||
const [key, { value }] = entry;
|
const [key, { value }] = entry;
|
||||||
|
|
||||||
// skip empty values (not allowed by AWS) or secrets that haven't changed
|
// skip empty values (not allowed by AWS) or secrets that haven't changed
|
||||||
@@ -167,7 +167,7 @@ export const AwsParameterStoreSyncFns = {
|
|||||||
for (const entry of Object.entries(awsParameterStoreSecretsRecord)) {
|
for (const entry of Object.entries(awsParameterStoreSecretsRecord)) {
|
||||||
const [key, parameter] = entry;
|
const [key, parameter] = entry;
|
||||||
|
|
||||||
if (!(key in affixedSecretMap) || !affixedSecretMap[key].value) {
|
if (!(key in secretMap) || !secretMap[key].value) {
|
||||||
parametersToDelete.push(parameter);
|
parametersToDelete.push(parameter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,7 +185,7 @@ export const AwsParameterStoreSyncFns = {
|
|||||||
Object.entries(awsParameterStoreSecretsRecord).map(([key, value]) => [key, { value: value.Value ?? "" }])
|
Object.entries(awsParameterStoreSecretsRecord).map(([key, value]) => [key, { value: value.Value ?? "" }])
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
removeSecrets: async (secretSync: TAwsParameterStoreSyncWithCredentials, affixedSecretMap: TSecretMap) => {
|
removeSecrets: async (secretSync: TAwsParameterStoreSyncWithCredentials, secretMap: TSecretMap) => {
|
||||||
const { destinationConfig } = secretSync;
|
const { destinationConfig } = secretSync;
|
||||||
|
|
||||||
const ssm = await getSSM(secretSync);
|
const ssm = await getSSM(secretSync);
|
||||||
@@ -197,7 +197,7 @@ export const AwsParameterStoreSyncFns = {
|
|||||||
for (const entry of Object.entries(awsParameterStoreSecretsRecord)) {
|
for (const entry of Object.entries(awsParameterStoreSecretsRecord)) {
|
||||||
const [key, param] = entry;
|
const [key, param] = entry;
|
||||||
|
|
||||||
if (key in affixedSecretMap) {
|
if (key in secretMap) {
|
||||||
parametersToDelete.push(param);
|
parametersToDelete.push(param);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import sodium from "libsodium-wrappers";
|
|||||||
|
|
||||||
import { getGitHubClient } from "@app/services/app-connection/github";
|
import { getGitHubClient } from "@app/services/app-connection/github";
|
||||||
import { GitHubSyncScope, GitHubSyncVisibility } from "@app/services/secret-sync/github/github-sync-enums";
|
import { GitHubSyncScope, GitHubSyncVisibility } from "@app/services/secret-sync/github/github-sync-enums";
|
||||||
|
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
|
||||||
import { SECRET_SYNC_NAME_MAP } from "@app/services/secret-sync/secret-sync-maps";
|
import { SECRET_SYNC_NAME_MAP } from "@app/services/secret-sync/secret-sync-maps";
|
||||||
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
|
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
|
||||||
|
|
||||||
@@ -158,7 +159,33 @@ const putSecret = async (client: Octokit, secretSync: TGitHubSyncWithCredentials
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const GithubSyncFns = {
|
export const GithubSyncFns = {
|
||||||
syncSecrets: async (secretSync: TGitHubSyncWithCredentials, affixedSecretMap: TSecretMap) => {
|
syncSecrets: async (secretSync: TGitHubSyncWithCredentials, secretMap: TSecretMap) => {
|
||||||
|
switch (secretSync.destinationConfig.scope) {
|
||||||
|
case GitHubSyncScope.Organization:
|
||||||
|
if (Object.values(secretMap).length > 1000) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
message: "GitHub does not support storing more than 1,000 secrets at the organization level.",
|
||||||
|
shouldRetry: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GitHubSyncScope.Repository:
|
||||||
|
case GitHubSyncScope.RepositoryEnvironment:
|
||||||
|
if (Object.values(secretMap).length > 100) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
message: "GitHub does not support storing more than 100 secrets at the repository level.",
|
||||||
|
shouldRetry: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`Unsupported GitHub Sync scope ${
|
||||||
|
(secretSync.destinationConfig as TGitHubSyncWithCredentials["destinationConfig"]).scope
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const client = getGitHubClient(secretSync.connection);
|
const client = getGitHubClient(secretSync.connection);
|
||||||
|
|
||||||
const encryptedSecrets = await getEncryptedSecrets(client, secretSync);
|
const encryptedSecrets = await getEncryptedSecrets(client, secretSync);
|
||||||
@@ -166,16 +193,16 @@ export const GithubSyncFns = {
|
|||||||
const publicKey = await getPublicKey(client, secretSync);
|
const publicKey = await getPublicKey(client, secretSync);
|
||||||
|
|
||||||
for await (const encryptedSecret of encryptedSecrets) {
|
for await (const encryptedSecret of encryptedSecrets) {
|
||||||
if (!(encryptedSecret.name in affixedSecretMap)) {
|
if (!(encryptedSecret.name in secretMap)) {
|
||||||
await deleteSecret(client, secretSync, encryptedSecret);
|
await deleteSecret(client, secretSync, encryptedSecret);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await sodium.ready.then(async () => {
|
await sodium.ready.then(async () => {
|
||||||
for await (const key of Object.keys(affixedSecretMap)) {
|
for await (const key of Object.keys(secretMap)) {
|
||||||
// convert secret & base64 key to Uint8Array.
|
// convert secret & base64 key to Uint8Array.
|
||||||
const binaryKey = sodium.from_base64(publicKey.key, sodium.base64_variants.ORIGINAL);
|
const binaryKey = sodium.from_base64(publicKey.key, sodium.base64_variants.ORIGINAL);
|
||||||
const binarySecretValue = sodium.from_string(affixedSecretMap[key].value);
|
const binarySecretValue = sodium.from_string(secretMap[key].value);
|
||||||
|
|
||||||
// encrypt secret using libsodium
|
// encrypt secret using libsodium
|
||||||
const encryptedBytes = sodium.crypto_box_seal(binarySecretValue, binaryKey);
|
const encryptedBytes = sodium.crypto_box_seal(binarySecretValue, binaryKey);
|
||||||
@@ -183,24 +210,31 @@ export const GithubSyncFns = {
|
|||||||
// convert encrypted Uint8Array to base64
|
// convert encrypted Uint8Array to base64
|
||||||
const encryptedSecretValue = sodium.to_base64(encryptedBytes, sodium.base64_variants.ORIGINAL);
|
const encryptedSecretValue = sodium.to_base64(encryptedBytes, sodium.base64_variants.ORIGINAL);
|
||||||
|
|
||||||
|
try {
|
||||||
await putSecret(client, secretSync, {
|
await putSecret(client, secretSync, {
|
||||||
secret_name: key,
|
secret_name: key,
|
||||||
encrypted_value: encryptedSecretValue,
|
encrypted_value: encryptedSecretValue,
|
||||||
key_id: publicKey.key_id
|
key_id: publicKey.key_id
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error,
|
||||||
|
secretKey: key
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getSecrets: async (secretSync: TGitHubSyncWithCredentials) => {
|
getSecrets: async (secretSync: TGitHubSyncWithCredentials) => {
|
||||||
throw new Error(`${SECRET_SYNC_NAME_MAP[secretSync.destination]} does not support importing secrets.`);
|
throw new Error(`${SECRET_SYNC_NAME_MAP[secretSync.destination]} does not support importing secrets.`);
|
||||||
},
|
},
|
||||||
removeSecrets: async (secretSync: TGitHubSyncWithCredentials, affixedSecretMap: TSecretMap) => {
|
removeSecrets: async (secretSync: TGitHubSyncWithCredentials, secretMap: TSecretMap) => {
|
||||||
const client = getGitHubClient(secretSync.connection);
|
const client = getGitHubClient(secretSync.connection);
|
||||||
|
|
||||||
const encryptedSecrets = await getEncryptedSecrets(client, secretSync);
|
const encryptedSecrets = await getEncryptedSecrets(client, secretSync);
|
||||||
|
|
||||||
for await (const encryptedSecret of encryptedSecrets) {
|
for await (const encryptedSecret of encryptedSecrets) {
|
||||||
if (encryptedSecret.name in affixedSecretMap) {
|
if (encryptedSecret.name in secretMap) {
|
||||||
await deleteSecret(client, secretSync, encryptedSecret);
|
await deleteSecret(client, secretSync, encryptedSecret);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,23 @@
|
|||||||
export class SecretSyncError extends Error {
|
export class SecretSyncError extends Error {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
error: unknown;
|
error?: unknown;
|
||||||
|
|
||||||
secretKey?: string;
|
secretKey?: string;
|
||||||
|
|
||||||
constructor({ name, error, secretKey }: { name?: string; error?: unknown; secretKey?: string } = {}) {
|
shouldRetry?: boolean;
|
||||||
super();
|
|
||||||
|
constructor({
|
||||||
|
name,
|
||||||
|
error,
|
||||||
|
secretKey,
|
||||||
|
message,
|
||||||
|
shouldRetry = true
|
||||||
|
}: { name?: string; error?: unknown; secretKey?: string; shouldRetry?: boolean; message?: string } = {}) {
|
||||||
|
super(message);
|
||||||
this.name = name || "SecretSyncError";
|
this.name = name || "SecretSyncError";
|
||||||
this.error = error;
|
this.error = error;
|
||||||
this.secretKey = secretKey;
|
this.secretKey = secretKey;
|
||||||
|
this.shouldRetry = shouldRetry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,55 +22,55 @@ export const listSecretSyncOptions = () => {
|
|||||||
return Object.values(SECRET_SYNC_LIST_OPTIONS).sort((a, b) => a.name.localeCompare(b.name));
|
return Object.values(SECRET_SYNC_LIST_OPTIONS).sort((a, b) => a.name.localeCompare(b.name));
|
||||||
};
|
};
|
||||||
|
|
||||||
const addAffixes = (secretSync: TSecretSyncWithCredentials, unprocessedSecretMap: TSecretMap) => {
|
// const addAffixes = (secretSync: TSecretSyncWithCredentials, unprocessedSecretMap: TSecretMap) => {
|
||||||
let secretMap = { ...unprocessedSecretMap };
|
// let secretMap = { ...unprocessedSecretMap };
|
||||||
|
//
|
||||||
const { appendSuffix, prependPrefix } = secretSync.syncOptions;
|
// const { appendSuffix, prependPrefix } = secretSync.syncOptions;
|
||||||
|
//
|
||||||
if (appendSuffix || prependPrefix) {
|
// if (appendSuffix || prependPrefix) {
|
||||||
secretMap = {};
|
// secretMap = {};
|
||||||
Object.entries(unprocessedSecretMap).forEach(([key, value]) => {
|
// Object.entries(unprocessedSecretMap).forEach(([key, value]) => {
|
||||||
secretMap[`${prependPrefix || ""}${key}${appendSuffix || ""}`] = value;
|
// secretMap[`${prependPrefix || ""}${key}${appendSuffix || ""}`] = value;
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
return secretMap;
|
// return secretMap;
|
||||||
};
|
// };
|
||||||
|
//
|
||||||
const stripAffixes = (secretSync: TSecretSyncWithCredentials, unprocessedSecretMap: TSecretMap) => {
|
// const stripAffixes = (secretSync: TSecretSyncWithCredentials, unprocessedSecretMap: TSecretMap) => {
|
||||||
let secretMap = { ...unprocessedSecretMap };
|
// let secretMap = { ...unprocessedSecretMap };
|
||||||
|
//
|
||||||
const { appendSuffix, prependPrefix } = secretSync.syncOptions;
|
// const { appendSuffix, prependPrefix } = secretSync.syncOptions;
|
||||||
|
//
|
||||||
if (appendSuffix || prependPrefix) {
|
// if (appendSuffix || prependPrefix) {
|
||||||
secretMap = {};
|
// secretMap = {};
|
||||||
Object.entries(unprocessedSecretMap).forEach(([key, value]) => {
|
// Object.entries(unprocessedSecretMap).forEach(([key, value]) => {
|
||||||
let processedKey = key;
|
// let processedKey = key;
|
||||||
|
//
|
||||||
if (prependPrefix && processedKey.startsWith(prependPrefix)) {
|
// if (prependPrefix && processedKey.startsWith(prependPrefix)) {
|
||||||
processedKey = processedKey.slice(prependPrefix.length);
|
// processedKey = processedKey.slice(prependPrefix.length);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
if (appendSuffix && processedKey.endsWith(appendSuffix)) {
|
// if (appendSuffix && processedKey.endsWith(appendSuffix)) {
|
||||||
processedKey = processedKey.slice(0, -appendSuffix.length);
|
// processedKey = processedKey.slice(0, -appendSuffix.length);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
secretMap[processedKey] = value;
|
// secretMap[processedKey] = value;
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
return secretMap;
|
// return secretMap;
|
||||||
};
|
// };
|
||||||
|
|
||||||
export const SecretSyncFns = {
|
export const SecretSyncFns = {
|
||||||
syncSecrets: (secretSync: TSecretSyncWithCredentials, secretMap: TSecretMap): Promise<void> => {
|
syncSecrets: (secretSync: TSecretSyncWithCredentials, secretMap: TSecretMap): Promise<void> => {
|
||||||
const affixedSecretMap = addAffixes(secretSync, secretMap);
|
// const affixedSecretMap = addAffixes(secretSync, secretMap);
|
||||||
|
|
||||||
switch (secretSync.destination) {
|
switch (secretSync.destination) {
|
||||||
case SecretSync.AWSParameterStore:
|
case SecretSync.AWSParameterStore:
|
||||||
return AwsParameterStoreSyncFns.syncSecrets(secretSync, affixedSecretMap);
|
return AwsParameterStoreSyncFns.syncSecrets(secretSync, secretMap);
|
||||||
case SecretSync.GitHub:
|
case SecretSync.GitHub:
|
||||||
return GithubSyncFns.syncSecrets(secretSync, affixedSecretMap);
|
return GithubSyncFns.syncSecrets(secretSync, secretMap);
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unhandled sync destination for push secrets: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
`Unhandled sync destination for push secrets: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
||||||
@@ -92,16 +92,17 @@ export const SecretSyncFns = {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return stripAffixes(secretSync, secretMap);
|
return secretMap;
|
||||||
|
// return stripAffixes(secretSync, secretMap);
|
||||||
},
|
},
|
||||||
removeSecrets: (secretSync: TSecretSyncWithCredentials, secretMap: TSecretMap): Promise<void> => {
|
removeSecrets: (secretSync: TSecretSyncWithCredentials, secretMap: TSecretMap): Promise<void> => {
|
||||||
const affixedSecretMap = addAffixes(secretSync, secretMap);
|
// const affixedSecretMap = addAffixes(secretSync, secretMap);
|
||||||
|
|
||||||
switch (secretSync.destination) {
|
switch (secretSync.destination) {
|
||||||
case SecretSync.AWSParameterStore:
|
case SecretSync.AWSParameterStore:
|
||||||
return AwsParameterStoreSyncFns.removeSecrets(secretSync, affixedSecretMap);
|
return AwsParameterStoreSyncFns.removeSecrets(secretSync, secretMap);
|
||||||
case SecretSync.GitHub:
|
case SecretSync.GitHub:
|
||||||
return GithubSyncFns.removeSecrets(secretSync, affixedSecretMap);
|
return GithubSyncFns.removeSecrets(secretSync, secretMap);
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unhandled sync destination for removing secrets: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
`Unhandled sync destination for removing secrets: ${(secretSync as TSecretSyncWithCredentials).destination}`
|
||||||
@@ -114,7 +115,7 @@ export const parseSyncErrorMessage = (err: unknown): string => {
|
|||||||
if (err instanceof SecretSyncError) {
|
if (err instanceof SecretSyncError) {
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
secretKey: err.secretKey,
|
secretKey: err.secretKey,
|
||||||
error: parseSyncErrorMessage(err.error)
|
error: err.message ?? parseSyncErrorMessage(err.error)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import {
|
|||||||
SecretSyncImportBehavior,
|
SecretSyncImportBehavior,
|
||||||
SecretSyncInitialSyncBehavior
|
SecretSyncInitialSyncBehavior
|
||||||
} from "@app/services/secret-sync/secret-sync-enums";
|
} from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
|
||||||
import { parseSyncErrorMessage, SecretSyncFns } from "@app/services/secret-sync/secret-sync-fns";
|
import { parseSyncErrorMessage, SecretSyncFns } from "@app/services/secret-sync/secret-sync-fns";
|
||||||
import { SECRET_SYNC_NAME_MAP } from "@app/services/secret-sync/secret-sync-maps";
|
import { SECRET_SYNC_NAME_MAP } from "@app/services/secret-sync/secret-sync-maps";
|
||||||
import {
|
import {
|
||||||
@@ -94,6 +95,19 @@ type SecretSyncActionJob = Job<
|
|||||||
TQueueSecretSyncSyncSecretsByIdDTO | TQueueSecretSyncImportSecretsByIdDTO | TQueueSecretSyncRemoveSecretsByIdDTO
|
TQueueSecretSyncSyncSecretsByIdDTO | TQueueSecretSyncImportSecretsByIdDTO | TQueueSecretSyncRemoveSecretsByIdDTO
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
const getRequeueDelay = (failureCount?: number) => {
|
||||||
|
if (!failureCount) return 0;
|
||||||
|
|
||||||
|
const baseDelay = 1000;
|
||||||
|
const maxDelay = 30000;
|
||||||
|
|
||||||
|
const delay = Math.min(baseDelay * 2 ** failureCount, maxDelay);
|
||||||
|
|
||||||
|
const jitter = delay * (0.5 + Math.random() * 0.5);
|
||||||
|
|
||||||
|
return jitter;
|
||||||
|
};
|
||||||
|
|
||||||
export const secretSyncQueueFactory = ({
|
export const secretSyncQueueFactory = ({
|
||||||
queueService,
|
queueService,
|
||||||
kmsService,
|
kmsService,
|
||||||
@@ -164,13 +178,18 @@ export const secretSyncQueueFactory = ({
|
|||||||
resourceMetadataDAL
|
resourceMetadataDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const $getSecrets = async (secretSync: TSecretSyncRaw | TSecretSyncWithCredentials, includeImports = true) => {
|
const $getInfisicalSecrets = async (
|
||||||
|
secretSync: TSecretSyncRaw | TSecretSyncWithCredentials,
|
||||||
|
includeImports = true
|
||||||
|
) => {
|
||||||
const { projectId, folderId, environment, folder } = secretSync;
|
const { projectId, folderId, environment, folder } = secretSync;
|
||||||
|
|
||||||
if (!folderId || !environment || !folder)
|
if (!folderId || !environment || !folder)
|
||||||
throw new Error(
|
throw new SecretSyncError({
|
||||||
"Invalid Secret Sync source configuration: folder no longer exists. Please update source environment and secret path."
|
message:
|
||||||
);
|
"Invalid Secret Sync source configuration: folder no longer exists. Please update source environment and secret path.",
|
||||||
|
shouldRetry: false
|
||||||
|
});
|
||||||
|
|
||||||
const secretMap: TSecretMap = {};
|
const secretMap: TSecretMap = {};
|
||||||
|
|
||||||
@@ -247,7 +266,7 @@ export const secretSyncQueueFactory = ({
|
|||||||
|
|
||||||
const queueSecretSyncSyncSecretsById = async (payload: TQueueSecretSyncSyncSecretsByIdDTO) =>
|
const queueSecretSyncSyncSecretsById = async (payload: TQueueSecretSyncSyncSecretsByIdDTO) =>
|
||||||
queueService.queue(QueueName.AppConnectionSecretSync, QueueJobs.SecretSyncSyncSecrets, payload, {
|
queueService.queue(QueueName.AppConnectionSecretSync, QueueJobs.SecretSyncSyncSecrets, payload, {
|
||||||
delay: payload.failedToAcquireLockCount ? 1000 : 0, // we don't want to delay initial job
|
delay: getRequeueDelay(payload.failedToAcquireLockCount), // this is for delaying re-queued jobs if sync is locked
|
||||||
attempts: 5,
|
attempts: 5,
|
||||||
backoff: {
|
backoff: {
|
||||||
type: "exponential",
|
type: "exponential",
|
||||||
@@ -309,7 +328,7 @@ export const secretSyncQueueFactory = ({
|
|||||||
|
|
||||||
const importedSecretMap: TSecretMap = {};
|
const importedSecretMap: TSecretMap = {};
|
||||||
|
|
||||||
const secretMap = await $getSecrets(secretSync, false);
|
const secretMap = await $getInfisicalSecrets(secretSync, false);
|
||||||
|
|
||||||
const secretsToCreate: Parameters<typeof $createManySecretsRawFn>[0]["secrets"] = [];
|
const secretsToCreate: Parameters<typeof $createManySecretsRawFn>[0]["secrets"] = [];
|
||||||
const secretsToUpdate: Parameters<typeof $updateManySecretsRawFn>[0]["secrets"] = [];
|
const secretsToUpdate: Parameters<typeof $updateManySecretsRawFn>[0]["secrets"] = [];
|
||||||
@@ -374,7 +393,7 @@ export const secretSyncQueueFactory = ({
|
|||||||
|
|
||||||
let isSynced = false;
|
let isSynced = false;
|
||||||
let syncMessage: string | null = null;
|
let syncMessage: string | null = null;
|
||||||
const isFinalAttempt = job.attemptsStarted === job.opts.attempts;
|
let isFinalAttempt = job.attemptsStarted === job.opts.attempts;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
@@ -400,7 +419,7 @@ export const secretSyncQueueFactory = ({
|
|||||||
syncOptions: { initialSyncBehavior }
|
syncOptions: { initialSyncBehavior }
|
||||||
} = secretSyncWithCredentials;
|
} = secretSyncWithCredentials;
|
||||||
|
|
||||||
const secretMap = await $getSecrets(secretSync);
|
const secretMap = await $getInfisicalSecrets(secretSync);
|
||||||
|
|
||||||
if (!lastSyncedAt && initialSyncBehavior !== SecretSyncInitialSyncBehavior.OverwriteDestination) {
|
if (!lastSyncedAt && initialSyncBehavior !== SecretSyncInitialSyncBehavior.OverwriteDestination) {
|
||||||
const importedSecretMap = await $importSecrets(
|
const importedSecretMap = await $importSecrets(
|
||||||
@@ -438,8 +457,12 @@ export const secretSyncQueueFactory = ({
|
|||||||
|
|
||||||
syncMessage = parseSyncErrorMessage(err);
|
syncMessage = parseSyncErrorMessage(err);
|
||||||
|
|
||||||
|
if (err instanceof SecretSyncError && !err.shouldRetry) {
|
||||||
|
isFinalAttempt = true;
|
||||||
|
} else {
|
||||||
// re-throw so job fails
|
// re-throw so job fails
|
||||||
throw err;
|
throw err;
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
const ranAt = new Date();
|
const ranAt = new Date();
|
||||||
const syncStatus = isSynced ? SecretSyncStatus.Succeeded : SecretSyncStatus.Failed;
|
const syncStatus = isSynced ? SecretSyncStatus.Succeeded : SecretSyncStatus.Failed;
|
||||||
@@ -639,7 +662,7 @@ export const secretSyncQueueFactory = ({
|
|||||||
kmsService
|
kmsService
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretMap = await $getSecrets(secretSync);
|
const secretMap = await $getInfisicalSecrets(secretSync);
|
||||||
|
|
||||||
await SecretSyncFns.removeSecrets(
|
await SecretSyncFns.removeSecrets(
|
||||||
{
|
{
|
||||||
@@ -816,7 +839,6 @@ export const secretSyncQueueFactory = ({
|
|||||||
case QueueJobs.SecretSyncSyncSecrets: {
|
case QueueJobs.SecretSyncSyncSecrets: {
|
||||||
const { failedToAcquireLockCount = 0, ...rest } = job.data as TQueueSecretSyncSyncSecretsByIdDTO;
|
const { failedToAcquireLockCount = 0, ...rest } = job.data as TQueueSecretSyncSyncSecretsByIdDTO;
|
||||||
|
|
||||||
//
|
|
||||||
if (failedToAcquireLockCount < 10) {
|
if (failedToAcquireLockCount < 10) {
|
||||||
await queueSecretSyncSyncSecretsById({ ...rest, failedToAcquireLockCount: failedToAcquireLockCount + 1 });
|
await queueSecretSyncSyncSecretsById({ ...rest, failedToAcquireLockCount: failedToAcquireLockCount + 1 });
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -13,19 +13,19 @@ const SyncOptionsSchema = (secretSync: SecretSync, options: TSyncOptionsConfig =
|
|||||||
initialSyncBehavior: (options.canImportSecrets
|
initialSyncBehavior: (options.canImportSecrets
|
||||||
? z.nativeEnum(SecretSyncInitialSyncBehavior)
|
? z.nativeEnum(SecretSyncInitialSyncBehavior)
|
||||||
: z.literal(SecretSyncInitialSyncBehavior.OverwriteDestination)
|
: z.literal(SecretSyncInitialSyncBehavior.OverwriteDestination)
|
||||||
).describe(SecretSyncs.SYNC_OPTIONS(secretSync).INITIAL_SYNC_BEHAVIOR),
|
).describe(SecretSyncs.SYNC_OPTIONS(secretSync).INITIAL_SYNC_BEHAVIOR)
|
||||||
prependPrefix: z
|
// prependPrefix: z
|
||||||
.string()
|
// .string()
|
||||||
.trim()
|
// .trim()
|
||||||
.transform((str) => str.toUpperCase())
|
// .transform((str) => str.toUpperCase())
|
||||||
.optional()
|
// .optional()
|
||||||
.describe(SecretSyncs.SYNC_OPTIONS(secretSync).PREPEND_PREFIX),
|
// .describe(SecretSyncs.SYNC_OPTIONS(secretSync).PREPEND_PREFIX),
|
||||||
appendSuffix: z
|
// appendSuffix: z
|
||||||
.string()
|
// .string()
|
||||||
.trim()
|
// .trim()
|
||||||
.transform((str) => str.toUpperCase())
|
// .transform((str) => str.toUpperCase())
|
||||||
.optional()
|
// .optional()
|
||||||
.describe(SecretSyncs.SYNC_OPTIONS(secretSync).APPEND_SUFFIX)
|
// .describe(SecretSyncs.SYNC_OPTIONS(secretSync).APPEND_SUFFIX)
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BaseSecretSyncSchema = (destination: SecretSync, syncOptionsConfig?: TSyncOptionsConfig) =>
|
export const BaseSecretSyncSchema = (destination: SecretSync, syncOptionsConfig?: TSyncOptionsConfig) =>
|
||||||
@@ -72,6 +72,7 @@ export const GenericCreateSecretSyncFieldsSchema = (destination: SecretSync, syn
|
|||||||
export const GenericUpdateSecretSyncFieldsSchema = (destination: SecretSync, syncOptionsConfig?: TSyncOptionsConfig) =>
|
export const GenericUpdateSecretSyncFieldsSchema = (destination: SecretSync, syncOptionsConfig?: TSyncOptionsConfig) =>
|
||||||
z.object({
|
z.object({
|
||||||
name: slugSchema({ field: "name" }).describe(SecretSyncs.UPDATE(destination).name).optional(),
|
name: slugSchema({ field: "name" }).describe(SecretSyncs.UPDATE(destination).name).optional(),
|
||||||
|
connectionId: z.string().uuid().describe(SecretSyncs.UPDATE(destination).connectionId).optional(),
|
||||||
description: z
|
description: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { OrgServiceActor } from "@app/lib/types";
|
|||||||
import { TAppConnectionServiceFactory } from "@app/services/app-connection/app-connection-service";
|
import { TAppConnectionServiceFactory } from "@app/services/app-connection/app-connection-service";
|
||||||
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
||||||
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
||||||
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
import { listSecretSyncOptions } from "@app/services/secret-sync/secret-sync-fns";
|
import { listSecretSyncOptions } from "@app/services/secret-sync/secret-sync-fns";
|
||||||
import {
|
import {
|
||||||
SecretSyncStatus,
|
SecretSyncStatus,
|
||||||
@@ -262,6 +263,13 @@ export const secretSyncServiceFactory = ({
|
|||||||
const updatedSecretSync = await secretSyncDAL.transaction(async (tx) => {
|
const updatedSecretSync = await secretSyncDAL.transaction(async (tx) => {
|
||||||
let { folderId } = secretSync;
|
let { folderId } = secretSync;
|
||||||
|
|
||||||
|
if (params.connectionId) {
|
||||||
|
const destinationApp = SECRET_SYNC_CONNECTION_MAP[secretSync.destination as SecretSync];
|
||||||
|
|
||||||
|
// validates permission to connect and app is valid for sync destination
|
||||||
|
await appConnectionService.connectAppConnectionById(destinationApp, params.connectionId, actor);
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(secretPath && secretPath !== secretSync.folder?.path) ||
|
(secretPath && secretPath !== secretSync.folder?.path) ||
|
||||||
(environment && environment !== secretSync.environment?.slug)
|
(environment && environment !== secretSync.environment?.slug)
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export type TCreateSecretSyncDTO = Pick<TSecretSync, "syncOptions" | "destinatio
|
|||||||
isEnabled?: boolean;
|
isEnabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TUpdateSecretSyncDTO = Partial<Omit<TCreateSecretSyncDTO, "connectionId" | "projectId">> & {
|
export type TUpdateSecretSyncDTO = Partial<Omit<TCreateSecretSyncDTO, "projectId">> & {
|
||||||
syncId: string;
|
syncId: string;
|
||||||
destination: SecretSync;
|
destination: SecretSync;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -40,8 +40,6 @@ description: "Learn how to configure an AWS Parameter Store Sync for Infisical."
|
|||||||
- **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 prior to syncing, prioritizing values present in Infisical if secrets conflict.
|
- **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint prior to syncing, prioritizing values present in Infisical if secrets conflict.
|
||||||
- **Import Secrets (Prioritize Parameter Store)**: Imports secrets from the destination endpoint prior to syncing, prioritizing values present in Parameter Store if secrets conflict.
|
- **Import Secrets (Prioritize Parameter Store)**: Imports secrets from the destination endpoint prior to syncing, prioritizing values present in Parameter Store if secrets conflict.
|
||||||
- **Prepend Prefix**: An optional prefix to prepend to secret keys when synced.
|
|
||||||
- **Append Suffix**: An optional suffix to append to secret keys when synced.
|
|
||||||
- **Enabled**: If enabled, secrets will automatically be synced from the source. Disable to prevent syncing until enabled.
|
- **Enabled**: If enabled, secrets will automatically be synced from the source. Disable to prevent syncing until enabled.
|
||||||
|
|
||||||
6. Configure the **Details** of your Parameter Store Sync, then click **Next**.
|
6. Configure the **Details** of your Parameter Store Sync, then click **Next**.
|
||||||
@@ -76,8 +74,7 @@ description: "Learn how to configure an AWS Parameter Store Sync for Infisical."
|
|||||||
"secretPath": "/my-secrets",
|
"secretPath": "/my-secrets",
|
||||||
"isEnabled": true,
|
"isEnabled": true,
|
||||||
"syncOptions": {
|
"syncOptions": {
|
||||||
"initialSyncBehavior": "overwrite-destination",
|
"initialSyncBehavior": "overwrite-destination"
|
||||||
"prependPrefix": "INF_",
|
|
||||||
},
|
},
|
||||||
"destinationConfig": {
|
"destinationConfig": {
|
||||||
"region": "us-east-1",
|
"region": "us-east-1",
|
||||||
@@ -113,8 +110,7 @@ description: "Learn how to configure an AWS Parameter Store Sync for Infisical."
|
|||||||
"lastRemoveMessage": null,
|
"lastRemoveMessage": null,
|
||||||
"lastRemovedAt": null,
|
"lastRemovedAt": null,
|
||||||
"syncOptions": {
|
"syncOptions": {
|
||||||
"initialSyncBehavior": "overwrite-destination",
|
"initialSyncBehavior": "overwrite-destination"
|
||||||
"prependPrefix": "INF_"
|
|
||||||
},
|
},
|
||||||
"projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
"projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
"connection": {
|
"connection": {
|
||||||
|
|||||||
@@ -62,8 +62,6 @@ description: "Learn how to configure a GitHub Sync for Infisical."
|
|||||||
<Note>
|
<Note>
|
||||||
GitHub does not support importing secrets.
|
GitHub does not support importing secrets.
|
||||||
</Note>
|
</Note>
|
||||||
- **Prepend Prefix**: An optional prefix to prepend to secret keys when synced.
|
|
||||||
- **Append Suffix**: An optional suffix to append to secret keys when synced.
|
|
||||||
- **Enabled**: If enabled, secrets will automatically be synced from the source. Disable to prevent syncing until enabled.
|
- **Enabled**: If enabled, secrets will automatically be synced from the source. Disable to prevent syncing until enabled.
|
||||||
|
|
||||||
6. Configure the **Details** of your GitHub Sync, then click **Next**.
|
6. Configure the **Details** of your GitHub Sync, then click **Next**.
|
||||||
@@ -97,13 +95,12 @@ description: "Learn how to configure a GitHub Sync for Infisical."
|
|||||||
"secretPath": "/my-secrets",
|
"secretPath": "/my-secrets",
|
||||||
"isEnabled": true,
|
"isEnabled": true,
|
||||||
"syncOptions": {
|
"syncOptions": {
|
||||||
"initialSyncBehavior": "overwrite-destination",
|
"initialSyncBehavior": "overwrite-destination"
|
||||||
"prependPrefix": "INF_",
|
|
||||||
},
|
},
|
||||||
"destinationConfig": {
|
"destinationConfig": {
|
||||||
"scope": "repository",
|
"scope": "repository",
|
||||||
"owner": "my-github",
|
"owner": "my-github",
|
||||||
"repo: "my-repository"
|
"repo": "my-repository"
|
||||||
}
|
}
|
||||||
}'
|
}'
|
||||||
```
|
```
|
||||||
@@ -135,8 +132,7 @@ description: "Learn how to configure a GitHub Sync for Infisical."
|
|||||||
"lastRemoveMessage": null,
|
"lastRemoveMessage": null,
|
||||||
"lastRemovedAt": null,
|
"lastRemovedAt": null,
|
||||||
"syncOptions": {
|
"syncOptions": {
|
||||||
"initialSyncBehavior": "overwrite-destination",
|
"initialSyncBehavior": "overwrite-destination"
|
||||||
"prependPrefix": "INF_"
|
|
||||||
},
|
},
|
||||||
"projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
"projectId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
|
||||||
"connection": {
|
"connection": {
|
||||||
@@ -157,7 +153,7 @@ description: "Learn how to configure a GitHub Sync for Infisical."
|
|||||||
"destinationConfig": {
|
"destinationConfig": {
|
||||||
"scope": "repository",
|
"scope": "repository",
|
||||||
"owner": "my-github",
|
"owner": "my-github",
|
||||||
"repo: "my-repository"
|
"repo": "my-repository"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { Button, FormControl, Switch } from "@app/components/v2";
|
import { Button, Checkbox, FormControl, Switch } from "@app/components/v2";
|
||||||
import { useWorkspace } from "@app/context";
|
import { useWorkspace } from "@app/context";
|
||||||
import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
|
import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
|
||||||
import {
|
import {
|
||||||
@@ -43,6 +43,7 @@ export const CreateSecretSyncForm = ({ destination, onComplete, onCancel }: Prop
|
|||||||
const { name: destinationName } = SECRET_SYNC_MAP[destination];
|
const { name: destinationName } = SECRET_SYNC_MAP[destination];
|
||||||
|
|
||||||
const [selectedTabIndex, setSelectedTabIndex] = useState(0);
|
const [selectedTabIndex, setSelectedTabIndex] = useState(0);
|
||||||
|
const [confirmOverwrite, setConfirmOverwrite] = useState(false);
|
||||||
|
|
||||||
const { syncOption } = useSecretSyncOption(destination);
|
const { syncOption } = useSecretSyncOption(destination);
|
||||||
|
|
||||||
@@ -93,7 +94,7 @@ export const CreateSecretSyncForm = ({ destination, onComplete, onCancel }: Prop
|
|||||||
setSelectedTabIndex((prev) => prev - 1);
|
setSelectedTabIndex((prev) => prev - 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { handleSubmit, trigger } = formMethods;
|
const { handleSubmit, trigger, watch, control } = formMethods;
|
||||||
|
|
||||||
const isStepValid = async (index: number) => trigger(FORM_TABS[index].fields);
|
const isStepValid = async (index: number) => trigger(FORM_TABS[index].fields);
|
||||||
|
|
||||||
@@ -122,7 +123,7 @@ export const CreateSecretSyncForm = ({ destination, onComplete, onCancel }: Prop
|
|||||||
return isEnabled;
|
return isEnabled;
|
||||||
};
|
};
|
||||||
|
|
||||||
const { control } = formMethods;
|
const initialSyncBehavior = watch("syncOptions.initialSyncBehavior");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className={twMerge(isFinalStep && "max-h-[70vh] overflow-y-auto")}>
|
<form className={twMerge(isFinalStep && "max-h-[70vh] overflow-y-auto")}>
|
||||||
@@ -195,10 +196,33 @@ export const CreateSecretSyncForm = ({ destination, onComplete, onCancel }: Prop
|
|||||||
</Tab.Panels>
|
</Tab.Panels>
|
||||||
</Tab.Group>
|
</Tab.Group>
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
|
{isFinalStep &&
|
||||||
|
initialSyncBehavior === SecretSyncInitialSyncBehavior.OverwriteDestination && (
|
||||||
|
<Checkbox
|
||||||
|
id="confirm-overwrite"
|
||||||
|
isChecked={confirmOverwrite}
|
||||||
|
containerClassName="-mt-5"
|
||||||
|
onCheckedChange={(isChecked) => setConfirmOverwrite(Boolean(isChecked))}
|
||||||
|
>
|
||||||
|
<p className={`mt-5 text-wrap ${confirmOverwrite ? "text-mineshaft-200" : "text-red"}`}>
|
||||||
|
I understand all secrets present in the configured {destinationName} destination will
|
||||||
|
be removed that are not present within Infisical.
|
||||||
|
</p>
|
||||||
|
</Checkbox>
|
||||||
|
)}
|
||||||
<div className="flex w-full flex-row-reverse justify-between gap-4 pt-4">
|
<div className="flex w-full flex-row-reverse justify-between gap-4 pt-4">
|
||||||
<Button onClick={handleNext} colorSchema="secondary">
|
<Button
|
||||||
|
isDisabled={
|
||||||
|
isFinalStep &&
|
||||||
|
initialSyncBehavior === SecretSyncInitialSyncBehavior.OverwriteDestination &&
|
||||||
|
!confirmOverwrite
|
||||||
|
}
|
||||||
|
onClick={handleNext}
|
||||||
|
colorSchema="secondary"
|
||||||
|
>
|
||||||
{isFinalStep ? "Create Sync" : "Next"}
|
{isFinalStep ? "Create Sync" : "Next"}
|
||||||
</Button>
|
</Button>
|
||||||
|
{}
|
||||||
{selectedTabIndex > 0 && (
|
{selectedTabIndex > 0 && (
|
||||||
<Button onClick={handlePrev} colorSchema="secondary">
|
<Button onClick={handlePrev} colorSchema="secondary">
|
||||||
Back
|
Back
|
||||||
|
|||||||
@@ -35,12 +35,13 @@ export const EditSecretSyncForm = ({ secretSync, fields, onComplete }: Props) =>
|
|||||||
reValidateMode: "onChange"
|
reValidateMode: "onChange"
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = async ({ environment, ...formData }: TSecretSyncForm) => {
|
const onSubmit = async ({ environment, connection, ...formData }: TSecretSyncForm) => {
|
||||||
try {
|
try {
|
||||||
const updatedSecretSync = await updateSecretSync.mutateAsync({
|
const updatedSecretSync = await updateSecretSync.mutateAsync({
|
||||||
syncId: secretSync.id,
|
syncId: secretSync.id,
|
||||||
...formData,
|
...formData,
|
||||||
environment: environment?.slug
|
environment: environment?.slug,
|
||||||
|
connectionId: connection.id
|
||||||
});
|
});
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Controller, useFormContext } from "react-hook-form";
|
|||||||
import { faTriangleExclamation } from "@fortawesome/free-solid-svg-icons";
|
import { faTriangleExclamation } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import { FormControl, Input, Select, SelectItem } from "@app/components/v2";
|
import { FormControl, Select, SelectItem } from "@app/components/v2";
|
||||||
import { SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP, SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
|
import { SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP, SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
|
||||||
import { useSecretSyncOption } from "@app/hooks/api/secretSyncs";
|
import { useSecretSyncOption } from "@app/hooks/api/secretSyncs";
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Controller
|
{/* <Controller
|
||||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
isError={Boolean(error)}
|
isError={Boolean(error)}
|
||||||
@@ -118,7 +118,7 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
|
|||||||
)}
|
)}
|
||||||
control={control}
|
control={control}
|
||||||
name="syncOptions.appendSuffix"
|
name="syncOptions.appendSuffix"
|
||||||
/>
|
/> */}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+6
-3
@@ -21,7 +21,10 @@ export const SecretSyncReviewFields = () => {
|
|||||||
connection,
|
connection,
|
||||||
environment,
|
environment,
|
||||||
secretPath,
|
secretPath,
|
||||||
syncOptions: { appendSuffix, prependPrefix, initialSyncBehavior },
|
syncOptions: {
|
||||||
|
// appendSuffix, prependPrefix,
|
||||||
|
initialSyncBehavior
|
||||||
|
},
|
||||||
destination,
|
destination,
|
||||||
isEnabled
|
isEnabled
|
||||||
} = watch();
|
} = watch();
|
||||||
@@ -72,8 +75,8 @@ export const SecretSyncReviewFields = () => {
|
|||||||
<SecretSyncLabel label="Initial Sync Behavior">
|
<SecretSyncLabel label="Initial Sync Behavior">
|
||||||
{SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP[initialSyncBehavior](destinationName).name}
|
{SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP[initialSyncBehavior](destinationName).name}
|
||||||
</SecretSyncLabel>
|
</SecretSyncLabel>
|
||||||
<SecretSyncLabel label="Prepend Prefix">{prependPrefix}</SecretSyncLabel>
|
{/* <SecretSyncLabel label="Prepend Prefix">{prependPrefix}</SecretSyncLabel>
|
||||||
<SecretSyncLabel label="Append Suffix">{appendSuffix}</SecretSyncLabel>
|
<SecretSyncLabel label="Append Suffix">{appendSuffix}</SecretSyncLabel> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
|
|||||||
@@ -13,17 +13,18 @@ const BaseSecretSyncSchema = z.object({
|
|||||||
environment: z.object({ slug: z.string(), id: z.string(), name: z.string() }),
|
environment: z.object({ slug: z.string(), id: z.string(), name: z.string() }),
|
||||||
secretPath: z.string().min(1, "Secret path required"),
|
secretPath: z.string().min(1, "Secret path required"),
|
||||||
syncOptions: z.object({
|
syncOptions: z.object({
|
||||||
initialSyncBehavior: z.nativeEnum(SecretSyncInitialSyncBehavior),
|
initialSyncBehavior: z.nativeEnum(SecretSyncInitialSyncBehavior)
|
||||||
prependPrefix: z
|
// scott: removed temporarily for evaluation of template formatting
|
||||||
.string()
|
// prependPrefix: z
|
||||||
.trim()
|
// .string()
|
||||||
.transform((str) => str.toUpperCase())
|
// .trim()
|
||||||
.optional(),
|
// .transform((str) => str.toUpperCase())
|
||||||
appendSuffix: z
|
// .optional(),
|
||||||
.string()
|
// appendSuffix: z
|
||||||
.trim()
|
// .string()
|
||||||
.transform((str) => str.toUpperCase())
|
// .trim()
|
||||||
.optional()
|
// .transform((str) => str.toUpperCase())
|
||||||
|
// .optional()
|
||||||
}),
|
}),
|
||||||
isEnabled: z.boolean()
|
isEnabled: z.boolean()
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export type TCreateSecretSyncDTO = DiscriminativePick<
|
|||||||
> & { environment: string; secretPath: string; projectId: string };
|
> & { environment: string; secretPath: string; projectId: string };
|
||||||
|
|
||||||
export type TUpdateSecretSyncDTO = Partial<
|
export type TUpdateSecretSyncDTO = Partial<
|
||||||
Omit<TCreateSecretSyncDTO, "connectionId" | "destination" | "projectId">
|
Omit<TCreateSecretSyncDTO, "destination" | "projectId">
|
||||||
> & {
|
> & {
|
||||||
destination: SecretSync;
|
destination: SecretSync;
|
||||||
syncId: string;
|
syncId: string;
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ export type TRootSecretSync = {
|
|||||||
lastRemoveMessage: string | null;
|
lastRemoveMessage: string | null;
|
||||||
syncOptions: {
|
syncOptions: {
|
||||||
initialSyncBehavior: SecretSyncInitialSyncBehavior;
|
initialSyncBehavior: SecretSyncInitialSyncBehavior;
|
||||||
prependPrefix?: string;
|
// prependPrefix?: string;
|
||||||
appendSuffix?: string;
|
// appendSuffix?: string;
|
||||||
};
|
};
|
||||||
connection: {
|
connection: {
|
||||||
app: AppConnection;
|
app: AppConnection;
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,7 @@ export const SecretSyncsTab = () => {
|
|||||||
const { data: secretSyncs = [], isPending: isSecretSyncsPending } = useListSecretSyncs(
|
const { data: secretSyncs = [], isPending: isSecretSyncsPending } = useListSecretSyncs(
|
||||||
currentWorkspace.id,
|
currentWorkspace.id,
|
||||||
{
|
{
|
||||||
refetchInterval: 2000
|
refetchInterval: 4000
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
+5
-2
@@ -66,7 +66,7 @@ const PageContent = () => {
|
|||||||
|
|
||||||
const handleEditSource = () => handlePopUpOpen("editSync", SecretSyncEditFields.Source);
|
const handleEditSource = () => handlePopUpOpen("editSync", SecretSyncEditFields.Source);
|
||||||
|
|
||||||
const handleEditOptions = () => handlePopUpOpen("editSync", SecretSyncEditFields.Options);
|
// const handleEditOptions = () => handlePopUpOpen("editSync", SecretSyncEditFields.Options);
|
||||||
|
|
||||||
const handleEditDestination = () => handlePopUpOpen("editSync", SecretSyncEditFields.Destination);
|
const handleEditDestination = () => handlePopUpOpen("editSync", SecretSyncEditFields.Destination);
|
||||||
|
|
||||||
@@ -109,7 +109,10 @@ const PageContent = () => {
|
|||||||
<div className="mr-4 flex w-72 flex-col gap-4">
|
<div className="mr-4 flex w-72 flex-col gap-4">
|
||||||
<SecretSyncDetailsSection secretSync={secretSync} onEditDetails={handleEditDetails} />
|
<SecretSyncDetailsSection secretSync={secretSync} onEditDetails={handleEditDetails} />
|
||||||
<SecretSyncSourceSection secretSync={secretSync} onEditSource={handleEditSource} />
|
<SecretSyncSourceSection secretSync={secretSync} onEditSource={handleEditSource} />
|
||||||
<SecretSyncOptionsSection secretSync={secretSync} onEditOptions={handleEditOptions} />
|
<SecretSyncOptionsSection
|
||||||
|
secretSync={secretSync}
|
||||||
|
// onEditOptions={handleEditOptions}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-1 flex-col gap-4">
|
<div className="flex flex-1 flex-col gap-4">
|
||||||
<SecretSyncDestinationSection
|
<SecretSyncDestinationSection
|
||||||
|
|||||||
+14
-17
@@ -1,24 +1,23 @@
|
|||||||
import { faEdit } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
|
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
|
||||||
import { SecretSyncLabel } from "@app/components/secret-syncs";
|
import { SecretSyncLabel } from "@app/components/secret-syncs";
|
||||||
import { IconButton } from "@app/components/v2";
|
|
||||||
import { ProjectPermissionSub } from "@app/context";
|
|
||||||
import { ProjectPermissionSecretSyncActions } from "@app/context/ProjectPermissionContext/types";
|
|
||||||
import { SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP } from "@app/helpers/secretSyncs";
|
import { SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP } from "@app/helpers/secretSyncs";
|
||||||
import { TSecretSync } from "@app/hooks/api/secretSyncs";
|
import { TSecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
secretSync: TSecretSync;
|
secretSync: TSecretSync;
|
||||||
onEditOptions: VoidFunction;
|
// onEditOptions: VoidFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) => {
|
export const SecretSyncOptionsSection = ({
|
||||||
|
secretSync
|
||||||
|
// onEditOptions
|
||||||
|
}: Props) => {
|
||||||
const {
|
const {
|
||||||
destination,
|
destination,
|
||||||
syncOptions: { appendSuffix, prependPrefix, initialSyncBehavior },
|
syncOptions: {
|
||||||
lastSyncedAt
|
// appendSuffix,
|
||||||
|
// prependPrefix,
|
||||||
|
initialSyncBehavior
|
||||||
|
}
|
||||||
} = secretSync;
|
} = secretSync;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -26,7 +25,7 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) =
|
|||||||
<div className="flex w-full flex-col gap-3 rounded-lg border border-mineshaft-600 bg-mineshaft-900 px-4 py-3">
|
<div className="flex w-full flex-col gap-3 rounded-lg border border-mineshaft-600 bg-mineshaft-900 px-4 py-3">
|
||||||
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-2">
|
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-2">
|
||||||
<h3 className="font-semibold text-mineshaft-100">Sync Options</h3>
|
<h3 className="font-semibold text-mineshaft-100">Sync Options</h3>
|
||||||
<ProjectPermissionCan
|
{/* <ProjectPermissionCan
|
||||||
I={ProjectPermissionSecretSyncActions.Edit}
|
I={ProjectPermissionSecretSyncActions.Edit}
|
||||||
a={ProjectPermissionSub.SecretSyncs}
|
a={ProjectPermissionSub.SecretSyncs}
|
||||||
>
|
>
|
||||||
@@ -41,17 +40,15 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) =
|
|||||||
<FontAwesomeIcon icon={faEdit} />
|
<FontAwesomeIcon icon={faEdit} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
</ProjectPermissionCan>
|
</ProjectPermissionCan> */}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{!lastSyncedAt && (
|
|
||||||
<SecretSyncLabel label="Initial Sync Behavior">
|
<SecretSyncLabel label="Initial Sync Behavior">
|
||||||
{SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP[initialSyncBehavior](destination).name}
|
{SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP[initialSyncBehavior](destination).name}
|
||||||
</SecretSyncLabel>
|
</SecretSyncLabel>
|
||||||
)}
|
{/* <SecretSyncLabel label="Prefix">{prependPrefix}</SecretSyncLabel>
|
||||||
<SecretSyncLabel label="Prefix">{prependPrefix}</SecretSyncLabel>
|
<SecretSyncLabel label="Suffix">{appendSuffix}</SecretSyncLabel> */}
|
||||||
<SecretSyncLabel label="Suffix">{appendSuffix}</SecretSyncLabel>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user