mirror of
https://github.com/Infisical/infisical.git
synced 2025-08-13 07:12:51 +00:00
Compare commits
4 Commits
feat/add-v
...
feat/add-o
Author | SHA1 | Date | |
---|---|---|---|
|
f4a1a00b59 | ||
|
b9933d711c | ||
|
847c2c67ec | ||
|
76a424dcfb |
@@ -1,21 +0,0 @@
|
|||||||
import { Knex } from "knex";
|
|
||||||
|
|
||||||
import { TableName } from "../schemas";
|
|
||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
|
||||||
const hasPitVersionLimitColumn = await knex.schema.hasColumn(TableName.Project, "pitVersionLimit");
|
|
||||||
await knex.schema.alterTable(TableName.Project, (tb) => {
|
|
||||||
if (!hasPitVersionLimitColumn) {
|
|
||||||
tb.integer("pitVersionLimit").notNullable().defaultTo(10);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
|
||||||
const hasPitVersionLimitColumn = await knex.schema.hasColumn(TableName.Project, "pitVersionLimit");
|
|
||||||
await knex.schema.alterTable(TableName.Project, (tb) => {
|
|
||||||
if (hasPitVersionLimitColumn) {
|
|
||||||
tb.dropColumn("pitVersionLimit");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
@@ -16,8 +16,7 @@ export const ProjectsSchema = z.object({
|
|||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
version: z.number().default(1),
|
version: z.number().default(1),
|
||||||
upgradeStatus: z.string().nullable().optional(),
|
upgradeStatus: z.string().nullable().optional()
|
||||||
pitVersionLimit: z.number().default(10)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TProjects = z.infer<typeof ProjectsSchema>;
|
export type TProjects = z.infer<typeof ProjectsSchema>;
|
||||||
|
@@ -81,7 +81,8 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||||
|
|
||||||
return snapshotDAL.countOfSnapshotsByFolderId(folder.id);
|
const count = await snapshotDAL.countOfSnapshotsByFolderId(folder.id);
|
||||||
|
return count;
|
||||||
};
|
};
|
||||||
|
|
||||||
const listSnapshots = async ({
|
const listSnapshots = async ({
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable no-await-in-loop */
|
|
||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
@@ -12,7 +11,6 @@ import {
|
|||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
||||||
import { logger } from "@app/lib/logger";
|
|
||||||
|
|
||||||
export type TSnapshotDALFactory = ReturnType<typeof snapshotDALFactory>;
|
export type TSnapshotDALFactory = ReturnType<typeof snapshotDALFactory>;
|
||||||
|
|
||||||
@@ -327,152 +325,12 @@ export const snapshotDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Prunes excess snapshots from the database to ensure only a specified number of recent snapshots are retained for each folder.
|
|
||||||
*
|
|
||||||
* This function operates in three main steps:
|
|
||||||
* 1. Pruning snapshots from root/non-versioned folders.
|
|
||||||
* 2. Pruning snapshots from versioned folders.
|
|
||||||
* 3. Removing orphaned snapshots that do not belong to any existing folder or folder version.
|
|
||||||
*
|
|
||||||
* The function processes snapshots in batches, determined by the `PRUNE_FOLDER_BATCH_SIZE` constant,
|
|
||||||
* to manage the large datasets without overwhelming the DB.
|
|
||||||
*
|
|
||||||
* Steps:
|
|
||||||
* - Fetch a batch of folder IDs.
|
|
||||||
* - For each batch, use a Common Table Expression (CTE) to rank snapshots within each folder by their creation date.
|
|
||||||
* - Identify and delete snapshots that exceed the project's point-in-time version limit (`pitVersionLimit`).
|
|
||||||
* - Repeat the process for versioned folders.
|
|
||||||
* - Finally, delete orphaned snapshots that do not have an associated folder.
|
|
||||||
*/
|
|
||||||
const pruneExcessSnapshots = async () => {
|
|
||||||
const PRUNE_FOLDER_BATCH_SIZE = 10000;
|
|
||||||
|
|
||||||
try {
|
|
||||||
let uuidOffset = "00000000-0000-0000-0000-000000000000";
|
|
||||||
// cleanup snapshots from root/non-versioned folders
|
|
||||||
// eslint-disable-next-line no-constant-condition, no-unreachable-loop
|
|
||||||
while (true) {
|
|
||||||
const folderBatch = await db(TableName.SecretFolder)
|
|
||||||
.where("id", ">", uuidOffset)
|
|
||||||
.where("isReserved", false)
|
|
||||||
.orderBy("id", "asc")
|
|
||||||
.limit(PRUNE_FOLDER_BATCH_SIZE)
|
|
||||||
.select("id");
|
|
||||||
|
|
||||||
const batchEntries = folderBatch.map((folder) => folder.id);
|
|
||||||
|
|
||||||
if (folderBatch.length) {
|
|
||||||
try {
|
|
||||||
logger.info(`Pruning snapshots in [range=${batchEntries[0]}:${batchEntries[batchEntries.length - 1]}]`);
|
|
||||||
await db(TableName.Snapshot)
|
|
||||||
.with("snapshot_cte", (qb) => {
|
|
||||||
void qb
|
|
||||||
.from(TableName.Snapshot)
|
|
||||||
.whereIn(`${TableName.Snapshot}.folderId`, batchEntries)
|
|
||||||
.select(
|
|
||||||
"folderId",
|
|
||||||
`${TableName.Snapshot}.id as id`,
|
|
||||||
db.raw(
|
|
||||||
`ROW_NUMBER() OVER (PARTITION BY ${TableName.Snapshot}."folderId" ORDER BY ${TableName.Snapshot}."createdAt" DESC) AS row_num`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.join(TableName.SecretFolder, `${TableName.SecretFolder}.id`, `${TableName.Snapshot}.folderId`)
|
|
||||||
.join(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretFolder}.envId`)
|
|
||||||
.join(TableName.Project, `${TableName.Project}.id`, `${TableName.Environment}.projectId`)
|
|
||||||
.join("snapshot_cte", "snapshot_cte.id", `${TableName.Snapshot}.id`)
|
|
||||||
.whereNull(`${TableName.SecretFolder}.parentId`)
|
|
||||||
.whereRaw(`snapshot_cte.row_num > ${TableName.Project}."pitVersionLimit"`)
|
|
||||||
.delete();
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(
|
|
||||||
`Failed to prune snapshots from root/non-versioned folders in range ${batchEntries[0]}:${
|
|
||||||
batchEntries[batchEntries.length - 1]
|
|
||||||
}`
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
uuidOffset = batchEntries[batchEntries.length - 1];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanup snapshots from versioned folders
|
|
||||||
uuidOffset = "00000000-0000-0000-0000-000000000000";
|
|
||||||
// eslint-disable-next-line no-constant-condition
|
|
||||||
while (true) {
|
|
||||||
const folderBatch = await db(TableName.SecretFolderVersion)
|
|
||||||
.select("folderId")
|
|
||||||
.distinct("folderId")
|
|
||||||
.where("folderId", ">", uuidOffset)
|
|
||||||
.orderBy("folderId", "asc")
|
|
||||||
.limit(PRUNE_FOLDER_BATCH_SIZE);
|
|
||||||
|
|
||||||
const batchEntries = folderBatch.map((folder) => folder.folderId);
|
|
||||||
|
|
||||||
if (folderBatch.length) {
|
|
||||||
try {
|
|
||||||
logger.info(`Pruning snapshots in range ${batchEntries[0]}:${batchEntries[batchEntries.length - 1]}`);
|
|
||||||
await db(TableName.Snapshot)
|
|
||||||
.with("snapshot_cte", (qb) => {
|
|
||||||
void qb
|
|
||||||
.from(TableName.Snapshot)
|
|
||||||
.whereIn(`${TableName.Snapshot}.folderId`, batchEntries)
|
|
||||||
.select(
|
|
||||||
"folderId",
|
|
||||||
`${TableName.Snapshot}.id as id`,
|
|
||||||
db.raw(
|
|
||||||
`ROW_NUMBER() OVER (PARTITION BY ${TableName.Snapshot}."folderId" ORDER BY ${TableName.Snapshot}."createdAt" DESC) AS row_num`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.join(
|
|
||||||
TableName.SecretFolderVersion,
|
|
||||||
`${TableName.SecretFolderVersion}.folderId`,
|
|
||||||
`${TableName.Snapshot}.folderId`
|
|
||||||
)
|
|
||||||
.join(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretFolderVersion}.envId`)
|
|
||||||
.join(TableName.Project, `${TableName.Project}.id`, `${TableName.Environment}.projectId`)
|
|
||||||
.join("snapshot_cte", "snapshot_cte.id", `${TableName.Snapshot}.id`)
|
|
||||||
.whereRaw(`snapshot_cte.row_num > ${TableName.Project}."pitVersionLimit"`)
|
|
||||||
.delete();
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(
|
|
||||||
`Failed to prune snapshots from versioned folders in range ${batchEntries[0]}:${
|
|
||||||
batchEntries[batchEntries.length - 1]
|
|
||||||
}`
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
uuidOffset = batchEntries[batchEntries.length - 1];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanup orphaned snapshots (those that don't belong to an existing folder and folder version)
|
|
||||||
await db(TableName.Snapshot)
|
|
||||||
.whereNotIn("folderId", (qb) => {
|
|
||||||
void qb
|
|
||||||
.select("folderId")
|
|
||||||
.from(TableName.SecretFolderVersion)
|
|
||||||
.union((qb1) => void qb1.select("id").from(TableName.SecretFolder));
|
|
||||||
})
|
|
||||||
.delete();
|
|
||||||
} catch (error) {
|
|
||||||
throw new DatabaseError({ error, name: "SnapshotPrune" });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...secretSnapshotOrm,
|
...secretSnapshotOrm,
|
||||||
findById,
|
findById,
|
||||||
findLatestSnapshotByFolderId,
|
findLatestSnapshotByFolderId,
|
||||||
findRecursivelySnapshots,
|
findRecursivelySnapshots,
|
||||||
countOfSnapshotsByFolderId,
|
countOfSnapshotsByFolderId,
|
||||||
findSecretSnapshotDataById,
|
findSecretSnapshotDataById
|
||||||
pruneExcessSnapshots
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -674,7 +674,8 @@ export const INTEGRATION = {
|
|||||||
secretGCPLabel: "The label for GCP secrets.",
|
secretGCPLabel: "The label for GCP secrets.",
|
||||||
secretAWSTag: "The tags for AWS secrets.",
|
secretAWSTag: "The tags for AWS secrets.",
|
||||||
kmsKeyId: "The ID of the encryption key from AWS KMS.",
|
kmsKeyId: "The ID of the encryption key from AWS KMS.",
|
||||||
shouldDisableDelete: "The flag to disable deletion of secrets in AWS Parameter Store."
|
shouldDisableDelete: "The flag to disable deletion of secrets in AWS Parameter Store.",
|
||||||
|
shouldEnableDelete: "The flag to enable deletion of secrets"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
|
@@ -824,9 +824,6 @@ export const registerRoutes = async (
|
|||||||
const dailyResourceCleanUp = dailyResourceCleanUpQueueServiceFactory({
|
const dailyResourceCleanUp = dailyResourceCleanUpQueueServiceFactory({
|
||||||
auditLogDAL,
|
auditLogDAL,
|
||||||
queueService,
|
queueService,
|
||||||
secretVersionDAL,
|
|
||||||
secretFolderVersionDAL: folderVersionDAL,
|
|
||||||
snapshotDAL,
|
|
||||||
identityAccessTokenDAL,
|
identityAccessTokenDAL,
|
||||||
secretSharingDAL
|
secretSharingDAL
|
||||||
});
|
});
|
||||||
|
@@ -8,7 +8,7 @@ import { writeLimit } from "@app/server/config/rateLimiter";
|
|||||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { IntegrationMappingBehavior } from "@app/services/integration-auth/integration-list";
|
import { IntegrationMetadataSchema } from "@app/services/integration/integration-schema";
|
||||||
import { PostHogEventTypes, TIntegrationCreatedEvent } from "@app/services/telemetry/telemetry-types";
|
import { PostHogEventTypes, TIntegrationCreatedEvent } from "@app/services/telemetry/telemetry-types";
|
||||||
|
|
||||||
export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
||||||
@@ -46,36 +46,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
|||||||
path: z.string().trim().optional().describe(INTEGRATION.CREATE.path),
|
path: z.string().trim().optional().describe(INTEGRATION.CREATE.path),
|
||||||
region: z.string().trim().optional().describe(INTEGRATION.CREATE.region),
|
region: z.string().trim().optional().describe(INTEGRATION.CREATE.region),
|
||||||
scope: z.string().trim().optional().describe(INTEGRATION.CREATE.scope),
|
scope: z.string().trim().optional().describe(INTEGRATION.CREATE.scope),
|
||||||
metadata: z
|
metadata: IntegrationMetadataSchema.default({})
|
||||||
.object({
|
|
||||||
secretPrefix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretPrefix),
|
|
||||||
secretSuffix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretSuffix),
|
|
||||||
initialSyncBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.initialSyncBehavoir),
|
|
||||||
mappingBehavior: z
|
|
||||||
.nativeEnum(IntegrationMappingBehavior)
|
|
||||||
.optional()
|
|
||||||
.describe(INTEGRATION.CREATE.metadata.mappingBehavior),
|
|
||||||
shouldAutoRedeploy: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldAutoRedeploy),
|
|
||||||
secretGCPLabel: z
|
|
||||||
.object({
|
|
||||||
labelName: z.string(),
|
|
||||||
labelValue: z.string()
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
.describe(INTEGRATION.CREATE.metadata.secretGCPLabel),
|
|
||||||
secretAWSTag: z
|
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
key: z.string(),
|
|
||||||
value: z.string()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.optional()
|
|
||||||
.describe(INTEGRATION.CREATE.metadata.secretAWSTag),
|
|
||||||
kmsKeyId: z.string().optional().describe(INTEGRATION.CREATE.metadata.kmsKeyId),
|
|
||||||
shouldDisableDelete: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldDisableDelete)
|
|
||||||
})
|
|
||||||
.default({})
|
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -161,33 +132,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
|
|||||||
targetEnvironment: z.string().trim().describe(INTEGRATION.UPDATE.targetEnvironment),
|
targetEnvironment: z.string().trim().describe(INTEGRATION.UPDATE.targetEnvironment),
|
||||||
owner: z.string().trim().describe(INTEGRATION.UPDATE.owner),
|
owner: z.string().trim().describe(INTEGRATION.UPDATE.owner),
|
||||||
environment: z.string().trim().describe(INTEGRATION.UPDATE.environment),
|
environment: z.string().trim().describe(INTEGRATION.UPDATE.environment),
|
||||||
metadata: z
|
metadata: IntegrationMetadataSchema.optional()
|
||||||
.object({
|
|
||||||
secretPrefix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretPrefix),
|
|
||||||
secretSuffix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretSuffix),
|
|
||||||
initialSyncBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.initialSyncBehavoir),
|
|
||||||
mappingBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.mappingBehavior),
|
|
||||||
shouldAutoRedeploy: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldAutoRedeploy),
|
|
||||||
secretGCPLabel: z
|
|
||||||
.object({
|
|
||||||
labelName: z.string(),
|
|
||||||
labelValue: z.string()
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
.describe(INTEGRATION.CREATE.metadata.secretGCPLabel),
|
|
||||||
secretAWSTag: z
|
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
key: z.string(),
|
|
||||||
value: z.string()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.optional()
|
|
||||||
.describe(INTEGRATION.CREATE.metadata.secretAWSTag),
|
|
||||||
kmsKeyId: z.string().optional().describe(INTEGRATION.CREATE.metadata.kmsKeyId),
|
|
||||||
shouldDisableDelete: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldDisableDelete)
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
@@ -334,44 +334,6 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
|
||||||
method: "PUT",
|
|
||||||
url: "/:workspaceSlug/version-limit",
|
|
||||||
config: {
|
|
||||||
rateLimit: writeLimit
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
params: z.object({
|
|
||||||
workspaceSlug: z.string().trim()
|
|
||||||
}),
|
|
||||||
body: z.object({
|
|
||||||
pitVersionLimit: z.number().min(1).max(100)
|
|
||||||
}),
|
|
||||||
response: {
|
|
||||||
200: z.object({
|
|
||||||
message: z.string(),
|
|
||||||
workspace: ProjectsSchema
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
|
||||||
handler: async (req) => {
|
|
||||||
const workspace = await server.services.project.updateVersionLimit({
|
|
||||||
actorId: req.permission.id,
|
|
||||||
actor: req.permission.type,
|
|
||||||
actorAuthMethod: req.permission.authMethod,
|
|
||||||
actorOrgId: req.permission.orgId,
|
|
||||||
pitVersionLimit: req.body.pitVersionLimit,
|
|
||||||
workspaceSlug: req.params.workspaceSlug
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
message: "Successfully changed workspace version limit",
|
|
||||||
workspace
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:workspaceId/integrations",
|
url: "/:workspaceId/integrations",
|
||||||
|
@@ -31,6 +31,7 @@ import { logger } from "@app/lib/logger";
|
|||||||
import { TCreateManySecretsRawFn, TUpdateManySecretsRawFn } from "@app/services/secret/secret-types";
|
import { TCreateManySecretsRawFn, TUpdateManySecretsRawFn } from "@app/services/secret/secret-types";
|
||||||
|
|
||||||
import { TIntegrationDALFactory } from "../integration/integration-dal";
|
import { TIntegrationDALFactory } from "../integration/integration-dal";
|
||||||
|
import { IntegrationMetadataSchema } from "../integration/integration-schema";
|
||||||
import {
|
import {
|
||||||
IntegrationInitialSyncBehavior,
|
IntegrationInitialSyncBehavior,
|
||||||
IntegrationMappingBehavior,
|
IntegrationMappingBehavior,
|
||||||
@@ -1363,38 +1364,41 @@ const syncSecretsGitHub = async ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for await (const encryptedSecret of encryptedSecrets) {
|
const metadata = IntegrationMetadataSchema.parse(integration.metadata);
|
||||||
if (
|
if (metadata.shouldEnableDelete) {
|
||||||
!(encryptedSecret.name in secrets) &&
|
for await (const encryptedSecret of encryptedSecrets) {
|
||||||
!(appendices?.prefix !== undefined && !encryptedSecret.name.startsWith(appendices?.prefix)) &&
|
if (
|
||||||
!(appendices?.suffix !== undefined && !encryptedSecret.name.endsWith(appendices?.suffix))
|
!(encryptedSecret.name in secrets) &&
|
||||||
) {
|
!(appendices?.prefix !== undefined && !encryptedSecret.name.startsWith(appendices?.prefix)) &&
|
||||||
switch (integration.scope) {
|
!(appendices?.suffix !== undefined && !encryptedSecret.name.endsWith(appendices?.suffix))
|
||||||
case GithubScope.Org: {
|
) {
|
||||||
await octokit.request("DELETE /orgs/{org}/actions/secrets/{secret_name}", {
|
switch (integration.scope) {
|
||||||
org: integration.owner as string,
|
case GithubScope.Org: {
|
||||||
secret_name: encryptedSecret.name
|
await octokit.request("DELETE /orgs/{org}/actions/secrets/{secret_name}", {
|
||||||
});
|
org: integration.owner as string,
|
||||||
break;
|
|
||||||
}
|
|
||||||
case GithubScope.Env: {
|
|
||||||
await octokit.request(
|
|
||||||
"DELETE /repositories/{repository_id}/environments/{environment_name}/secrets/{secret_name}",
|
|
||||||
{
|
|
||||||
repository_id: Number(integration.appId),
|
|
||||||
environment_name: integration.targetEnvironmentId as string,
|
|
||||||
secret_name: encryptedSecret.name
|
secret_name: encryptedSecret.name
|
||||||
}
|
});
|
||||||
);
|
break;
|
||||||
break;
|
}
|
||||||
}
|
case GithubScope.Env: {
|
||||||
default: {
|
await octokit.request(
|
||||||
await octokit.request("DELETE /repos/{owner}/{repo}/actions/secrets/{secret_name}", {
|
"DELETE /repositories/{repository_id}/environments/{environment_name}/secrets/{secret_name}",
|
||||||
owner: integration.owner as string,
|
{
|
||||||
repo: integration.app as string,
|
repository_id: Number(integration.appId),
|
||||||
secret_name: encryptedSecret.name
|
environment_name: integration.targetEnvironmentId as string,
|
||||||
});
|
secret_name: encryptedSecret.name
|
||||||
break;
|
}
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
await octokit.request("DELETE /repos/{owner}/{repo}/actions/secrets/{secret_name}", {
|
||||||
|
owner: integration.owner as string,
|
||||||
|
repo: integration.app as string,
|
||||||
|
secret_name: encryptedSecret.name
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2750,20 +2754,6 @@ const syncSecretsCloudflarePages = async ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const metadata = z.record(z.any()).parse(integration.metadata);
|
|
||||||
if (metadata.shouldAutoRedeploy) {
|
|
||||||
await request.post(
|
|
||||||
`${IntegrationUrls.CLOUDFLARE_PAGES_API_URL}/client/v4/accounts/${accessId}/pages/projects/${integration.app}/deployments`,
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${accessToken}`,
|
|
||||||
Accept: "application/json"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
35
backend/src/services/integration/integration-schema.ts
Normal file
35
backend/src/services/integration/integration-schema.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { INTEGRATION } from "@app/lib/api-docs";
|
||||||
|
|
||||||
|
import { IntegrationMappingBehavior } from "../integration-auth/integration-list";
|
||||||
|
|
||||||
|
export const IntegrationMetadataSchema = z.object({
|
||||||
|
secretPrefix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretPrefix),
|
||||||
|
secretSuffix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretSuffix),
|
||||||
|
initialSyncBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.initialSyncBehavoir),
|
||||||
|
mappingBehavior: z
|
||||||
|
.nativeEnum(IntegrationMappingBehavior)
|
||||||
|
.optional()
|
||||||
|
.describe(INTEGRATION.CREATE.metadata.mappingBehavior),
|
||||||
|
shouldAutoRedeploy: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldAutoRedeploy),
|
||||||
|
secretGCPLabel: z
|
||||||
|
.object({
|
||||||
|
labelName: z.string(),
|
||||||
|
labelValue: z.string()
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
.describe(INTEGRATION.CREATE.metadata.secretGCPLabel),
|
||||||
|
secretAWSTag: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
key: z.string(),
|
||||||
|
value: z.string()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.optional()
|
||||||
|
.describe(INTEGRATION.CREATE.metadata.secretAWSTag),
|
||||||
|
kmsKeyId: z.string().optional().describe(INTEGRATION.CREATE.metadata.kmsKeyId),
|
||||||
|
shouldDisableDelete: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldDisableDelete),
|
||||||
|
shouldEnableDelete: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldEnableDelete)
|
||||||
|
});
|
@@ -29,6 +29,7 @@ export type TCreateIntegrationDTO = {
|
|||||||
}[];
|
}[];
|
||||||
kmsKeyId?: string;
|
kmsKeyId?: string;
|
||||||
shouldDisableDelete?: boolean;
|
shouldDisableDelete?: boolean;
|
||||||
|
shouldEnableDelete?: boolean;
|
||||||
};
|
};
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
@@ -54,6 +55,7 @@ export type TUpdateIntegrationDTO = {
|
|||||||
}[];
|
}[];
|
||||||
kmsKeyId?: string;
|
kmsKeyId?: string;
|
||||||
shouldDisableDelete?: boolean;
|
shouldDisableDelete?: boolean;
|
||||||
|
shouldEnableDelete?: boolean;
|
||||||
};
|
};
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
@@ -39,7 +39,6 @@ import {
|
|||||||
TToggleProjectAutoCapitalizationDTO,
|
TToggleProjectAutoCapitalizationDTO,
|
||||||
TUpdateProjectDTO,
|
TUpdateProjectDTO,
|
||||||
TUpdateProjectNameDTO,
|
TUpdateProjectNameDTO,
|
||||||
TUpdateProjectVersionLimitDTO,
|
|
||||||
TUpgradeProjectDTO
|
TUpgradeProjectDTO
|
||||||
} from "./project-types";
|
} from "./project-types";
|
||||||
|
|
||||||
@@ -134,8 +133,7 @@ export const projectServiceFactory = ({
|
|||||||
name: workspaceName,
|
name: workspaceName,
|
||||||
orgId: organization.id,
|
orgId: organization.id,
|
||||||
slug: projectSlug || slugify(`${workspaceName}-${alphaNumericNanoId(4)}`),
|
slug: projectSlug || slugify(`${workspaceName}-${alphaNumericNanoId(4)}`),
|
||||||
version: ProjectVersion.V2,
|
version: ProjectVersion.V2
|
||||||
pitVersionLimit: 10
|
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@@ -408,35 +406,6 @@ export const projectServiceFactory = ({
|
|||||||
return updatedProject;
|
return updatedProject;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateVersionLimit = async ({
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
actorOrgId,
|
|
||||||
actorAuthMethod,
|
|
||||||
pitVersionLimit,
|
|
||||||
workspaceSlug
|
|
||||||
}: TUpdateProjectVersionLimitDTO) => {
|
|
||||||
const project = await projectDAL.findProjectBySlug(workspaceSlug, actorOrgId);
|
|
||||||
if (!project) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Project not found"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const { hasRole } = await permissionService.getProjectPermission(
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
project.id,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!hasRole(ProjectMembershipRole.Admin))
|
|
||||||
throw new BadRequestError({ message: "Only admins are allowed to take this action" });
|
|
||||||
|
|
||||||
return projectDAL.updateById(project.id, { pitVersionLimit });
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateName = async ({
|
const updateName = async ({
|
||||||
projectId,
|
projectId,
|
||||||
actor,
|
actor,
|
||||||
@@ -532,7 +501,6 @@ export const projectServiceFactory = ({
|
|||||||
getAProject,
|
getAProject,
|
||||||
toggleAutoCapitalization,
|
toggleAutoCapitalization,
|
||||||
updateName,
|
updateName,
|
||||||
upgradeProject,
|
upgradeProject
|
||||||
updateVersionLimit
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -43,11 +43,6 @@ export type TToggleProjectAutoCapitalizationDTO = {
|
|||||||
autoCapitalization: boolean;
|
autoCapitalization: boolean;
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
export type TUpdateProjectVersionLimitDTO = {
|
|
||||||
pitVersionLimit: number;
|
|
||||||
workspaceSlug: string;
|
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
|
||||||
|
|
||||||
export type TUpdateProjectNameDTO = {
|
export type TUpdateProjectNameDTO = {
|
||||||
name: string;
|
name: string;
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
@@ -1,19 +1,13 @@
|
|||||||
import { TAuditLogDALFactory } from "@app/ee/services/audit-log/audit-log-dal";
|
import { TAuditLogDALFactory } from "@app/ee/services/audit-log/audit-log-dal";
|
||||||
import { TSnapshotDALFactory } from "@app/ee/services/secret-snapshot/snapshot-dal";
|
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
||||||
|
|
||||||
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
|
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
|
||||||
import { TSecretVersionDALFactory } from "../secret/secret-version-dal";
|
|
||||||
import { TSecretFolderVersionDALFactory } from "../secret-folder/secret-folder-version-dal";
|
|
||||||
import { TSecretSharingDALFactory } from "../secret-sharing/secret-sharing-dal";
|
import { TSecretSharingDALFactory } from "../secret-sharing/secret-sharing-dal";
|
||||||
|
|
||||||
type TDailyResourceCleanUpQueueServiceFactoryDep = {
|
type TDailyResourceCleanUpQueueServiceFactoryDep = {
|
||||||
auditLogDAL: Pick<TAuditLogDALFactory, "pruneAuditLog">;
|
auditLogDAL: Pick<TAuditLogDALFactory, "pruneAuditLog">;
|
||||||
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "removeExpiredTokens">;
|
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "removeExpiredTokens">;
|
||||||
secretVersionDAL: Pick<TSecretVersionDALFactory, "pruneExcessVersions">;
|
|
||||||
secretFolderVersionDAL: Pick<TSecretFolderVersionDALFactory, "pruneExcessVersions">;
|
|
||||||
snapshotDAL: Pick<TSnapshotDALFactory, "pruneExcessSnapshots">;
|
|
||||||
secretSharingDAL: Pick<TSecretSharingDALFactory, "pruneExpiredSharedSecrets">;
|
secretSharingDAL: Pick<TSecretSharingDALFactory, "pruneExpiredSharedSecrets">;
|
||||||
queueService: TQueueServiceFactory;
|
queueService: TQueueServiceFactory;
|
||||||
};
|
};
|
||||||
@@ -23,9 +17,6 @@ export type TDailyResourceCleanUpQueueServiceFactory = ReturnType<typeof dailyRe
|
|||||||
export const dailyResourceCleanUpQueueServiceFactory = ({
|
export const dailyResourceCleanUpQueueServiceFactory = ({
|
||||||
auditLogDAL,
|
auditLogDAL,
|
||||||
queueService,
|
queueService,
|
||||||
snapshotDAL,
|
|
||||||
secretVersionDAL,
|
|
||||||
secretFolderVersionDAL,
|
|
||||||
identityAccessTokenDAL,
|
identityAccessTokenDAL,
|
||||||
secretSharingDAL
|
secretSharingDAL
|
||||||
}: TDailyResourceCleanUpQueueServiceFactoryDep) => {
|
}: TDailyResourceCleanUpQueueServiceFactoryDep) => {
|
||||||
@@ -34,9 +25,6 @@ export const dailyResourceCleanUpQueueServiceFactory = ({
|
|||||||
await auditLogDAL.pruneAuditLog();
|
await auditLogDAL.pruneAuditLog();
|
||||||
await identityAccessTokenDAL.removeExpiredTokens();
|
await identityAccessTokenDAL.removeExpiredTokens();
|
||||||
await secretSharingDAL.pruneExpiredSharedSecrets();
|
await secretSharingDAL.pruneExpiredSharedSecrets();
|
||||||
await snapshotDAL.pruneExcessSnapshots();
|
|
||||||
await secretVersionDAL.pruneExcessVersions();
|
|
||||||
await secretFolderVersionDAL.pruneExcessVersions();
|
|
||||||
logger.info(`${QueueName.DailyResourceCleanUp}: queue task completed`);
|
logger.info(`${QueueName.DailyResourceCleanUp}: queue task completed`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -62,32 +62,5 @@ export const secretFolderVersionDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const pruneExcessVersions = async () => {
|
return { ...secretFolderVerOrm, findLatestFolderVersions, findLatestVersionByFolderId };
|
||||||
try {
|
|
||||||
await db(TableName.SecretFolderVersion)
|
|
||||||
.with("folder_cte", (qb) => {
|
|
||||||
void qb
|
|
||||||
.from(TableName.SecretFolderVersion)
|
|
||||||
.select(
|
|
||||||
"id",
|
|
||||||
"folderId",
|
|
||||||
db.raw(
|
|
||||||
`ROW_NUMBER() OVER (PARTITION BY ${TableName.SecretFolderVersion}."folderId" ORDER BY ${TableName.SecretFolderVersion}."createdAt" DESC) AS row_num`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.join(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretFolderVersion}.envId`)
|
|
||||||
.join(TableName.Project, `${TableName.Project}.id`, `${TableName.Environment}.projectId`)
|
|
||||||
.join("folder_cte", "folder_cte.id", `${TableName.SecretFolderVersion}.id`)
|
|
||||||
.whereRaw(`folder_cte.row_num > ${TableName.Project}."pitVersionLimit"`)
|
|
||||||
.delete();
|
|
||||||
} catch (error) {
|
|
||||||
throw new DatabaseError({
|
|
||||||
error,
|
|
||||||
name: "Secret Folder Version Prune"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return { ...secretFolderVerOrm, findLatestFolderVersions, findLatestVersionByFolderId, pruneExcessVersions };
|
|
||||||
};
|
};
|
||||||
|
@@ -111,37 +111,8 @@ export const secretVersionDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const pruneExcessVersions = async () => {
|
|
||||||
try {
|
|
||||||
await db(TableName.SecretVersion)
|
|
||||||
.with("version_cte", (qb) => {
|
|
||||||
void qb
|
|
||||||
.from(TableName.SecretVersion)
|
|
||||||
.select(
|
|
||||||
"id",
|
|
||||||
"folderId",
|
|
||||||
db.raw(
|
|
||||||
`ROW_NUMBER() OVER (PARTITION BY ${TableName.SecretVersion}."secretId" ORDER BY ${TableName.SecretVersion}."createdAt" DESC) AS row_num`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.join(TableName.SecretFolder, `${TableName.SecretFolder}.id`, `${TableName.SecretVersion}.folderId`)
|
|
||||||
.join(TableName.Environment, `${TableName.Environment}.id`, `${TableName.SecretFolder}.envId`)
|
|
||||||
.join(TableName.Project, `${TableName.Project}.id`, `${TableName.Environment}.projectId`)
|
|
||||||
.join("version_cte", "version_cte.id", `${TableName.SecretVersion}.id`)
|
|
||||||
.whereRaw(`version_cte.row_num > ${TableName.Project}."pitVersionLimit"`)
|
|
||||||
.delete();
|
|
||||||
} catch (error) {
|
|
||||||
throw new DatabaseError({
|
|
||||||
error,
|
|
||||||
name: "Secret Version Prune"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...secretVersionOrm,
|
...secretVersionOrm,
|
||||||
pruneExcessVersions,
|
|
||||||
findLatestVersionMany,
|
findLatestVersionMany,
|
||||||
bulkUpdate,
|
bulkUpdate,
|
||||||
findLatestVersionByFolderId,
|
findLatestVersionByFolderId,
|
||||||
|
@@ -496,6 +496,7 @@ To enable auto redeployment you simply have to add the following annotation to t
|
|||||||
```yaml
|
```yaml
|
||||||
secrets.infisical.com/auto-reload: "true"
|
secrets.infisical.com/auto-reload: "true"
|
||||||
```
|
```
|
||||||
|
|
||||||
<Accordion title="Deployment example with auto redeploy enabled">
|
<Accordion title="Deployment example with auto redeploy enabled">
|
||||||
```yaml
|
```yaml
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
@@ -526,11 +527,7 @@ spec:
|
|||||||
- containerPort: 80
|
- containerPort: 80
|
||||||
```
|
```
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Info>
|
|
||||||
#### How it works
|
|
||||||
When a secret change occurs, the operator will check to see which deployments are using the operator-managed Kubernetes secret that received the update.
|
|
||||||
Then, for each deployment that has this annotation present, a rolling update will be triggered.
|
|
||||||
</Info>
|
|
||||||
## Global configuration
|
## Global configuration
|
||||||
|
|
||||||
To configure global settings that will apply to all instances of `InfisicalSecret`, you can define these configurations in a Kubernetes ConfigMap.
|
To configure global settings that will apply to all instances of `InfisicalSecret`, you can define these configurations in a Kubernetes ConfigMap.
|
||||||
|
@@ -73,6 +73,7 @@ export const useCreateIntegration = () => {
|
|||||||
}[];
|
}[];
|
||||||
kmsKeyId?: string;
|
kmsKeyId?: string;
|
||||||
shouldDisableDelete?: boolean;
|
shouldDisableDelete?: boolean;
|
||||||
|
shouldEnableDelete?: boolean;
|
||||||
};
|
};
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
|
@@ -20,7 +20,6 @@ import {
|
|||||||
TUpdateWorkspaceIdentityRoleDTO,
|
TUpdateWorkspaceIdentityRoleDTO,
|
||||||
TUpdateWorkspaceUserRoleDTO,
|
TUpdateWorkspaceUserRoleDTO,
|
||||||
UpdateEnvironmentDTO,
|
UpdateEnvironmentDTO,
|
||||||
UpdatePitVersionLimitDTO,
|
|
||||||
Workspace
|
Workspace
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
@@ -250,21 +249,6 @@ export const useToggleAutoCapitalization = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useUpdateWorkspaceVersionLimit = () => {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
return useMutation<{}, {}, UpdatePitVersionLimitDTO>({
|
|
||||||
mutationFn: ({ projectSlug, pitVersionLimit }) => {
|
|
||||||
return apiRequest.put(`/api/v1/workspace/${projectSlug}/version-limit`, {
|
|
||||||
pitVersionLimit
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onSuccess: () => {
|
|
||||||
queryClient.invalidateQueries(workspaceKeys.getAllUserWorkspace);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useDeleteWorkspace = () => {
|
export const useDeleteWorkspace = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
@@ -16,7 +16,6 @@ export type Workspace = {
|
|||||||
upgradeStatus: string | null;
|
upgradeStatus: string | null;
|
||||||
autoCapitalization: boolean;
|
autoCapitalization: boolean;
|
||||||
environments: WorkspaceEnv[];
|
environments: WorkspaceEnv[];
|
||||||
pitVersionLimit: number;
|
|
||||||
slug: string;
|
slug: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -49,7 +48,6 @@ export type CreateWorkspaceDTO = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type RenameWorkspaceDTO = { workspaceID: string; newWorkspaceName: string };
|
export type RenameWorkspaceDTO = { workspaceID: string; newWorkspaceName: string };
|
||||||
export type UpdatePitVersionLimitDTO = { projectSlug: string; pitVersionLimit: number };
|
|
||||||
export type ToggleAutoCapitalizationDTO = { workspaceID: string; state: boolean };
|
export type ToggleAutoCapitalizationDTO = { workspaceID: string; state: boolean };
|
||||||
|
|
||||||
export type DeleteWorkspaceDTO = { workspaceID: string };
|
export type DeleteWorkspaceDTO = { workspaceID: string };
|
||||||
@@ -130,4 +128,4 @@ export type TUpdateWorkspaceGroupRoleDTO = {
|
|||||||
temporaryAccessStartTime: string;
|
temporaryAccessStartTime: string;
|
||||||
}
|
}
|
||||||
)[];
|
)[];
|
||||||
};
|
};
|
@@ -7,15 +7,7 @@ import { createNotification } from "@app/components/notifications";
|
|||||||
import { SecretPathInput } from "@app/components/v2/SecretPathInput";
|
import { SecretPathInput } from "@app/components/v2/SecretPathInput";
|
||||||
import { useCreateIntegration, useGetWorkspaceById } from "@app/hooks/api";
|
import { useCreateIntegration, useGetWorkspaceById } from "@app/hooks/api";
|
||||||
|
|
||||||
import {
|
import { Button, Card, CardTitle, FormControl, Select, SelectItem } from "../../../components/v2";
|
||||||
Button,
|
|
||||||
Card,
|
|
||||||
CardTitle,
|
|
||||||
FormControl,
|
|
||||||
Select,
|
|
||||||
SelectItem,
|
|
||||||
Switch
|
|
||||||
} from "../../../components/v2";
|
|
||||||
import {
|
import {
|
||||||
useGetIntegrationAuthApps,
|
useGetIntegrationAuthApps,
|
||||||
useGetIntegrationAuthById
|
useGetIntegrationAuthById
|
||||||
@@ -42,7 +34,6 @@ export default function CloudflarePagesIntegrationPage() {
|
|||||||
const [targetApp, setTargetApp] = useState("");
|
const [targetApp, setTargetApp] = useState("");
|
||||||
const [targetAppId, setTargetAppId] = useState("");
|
const [targetAppId, setTargetAppId] = useState("");
|
||||||
const [targetEnvironment, setTargetEnvironment] = useState("");
|
const [targetEnvironment, setTargetEnvironment] = useState("");
|
||||||
const [shouldAutoRedeploy, setShouldAutoRedeploy] = useState(false);
|
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
@@ -78,10 +69,7 @@ export default function CloudflarePagesIntegrationPage() {
|
|||||||
appId: targetAppId,
|
appId: targetAppId,
|
||||||
sourceEnvironment: selectedSourceEnvironment,
|
sourceEnvironment: selectedSourceEnvironment,
|
||||||
targetEnvironment,
|
targetEnvironment,
|
||||||
secretPath,
|
secretPath
|
||||||
metadata: {
|
|
||||||
shouldAutoRedeploy
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@@ -181,15 +169,6 @@ export default function CloudflarePagesIntegrationPage() {
|
|||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<div className="mb-[2.36rem] ml-1 px-6">
|
|
||||||
<Switch
|
|
||||||
id="redeploy-cloudflare-pages"
|
|
||||||
onCheckedChange={(isChecked: boolean) => setShouldAutoRedeploy(isChecked)}
|
|
||||||
isChecked={shouldAutoRedeploy}
|
|
||||||
>
|
|
||||||
Auto-redeploy service upon secret change
|
|
||||||
</Switch>
|
|
||||||
</div>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
|
@@ -33,6 +33,7 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
Select,
|
Select,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
|
Switch,
|
||||||
Tab,
|
Tab,
|
||||||
TabList,
|
TabList,
|
||||||
TabPanel,
|
TabPanel,
|
||||||
@@ -59,7 +60,7 @@ const schema = yup.object({
|
|||||||
selectedSourceEnvironment: yup.string().trim().required("Project Environment is required"),
|
selectedSourceEnvironment: yup.string().trim().required("Project Environment is required"),
|
||||||
secretPath: yup.string().trim().required("Secrets Path is required"),
|
secretPath: yup.string().trim().required("Secrets Path is required"),
|
||||||
secretSuffix: yup.string().trim().optional(),
|
secretSuffix: yup.string().trim().optional(),
|
||||||
|
shouldEnableDelete: yup.boolean().optional(),
|
||||||
scope: yup.mixed<TargetEnv>().oneOf(targetEnv.slice()).required(),
|
scope: yup.mixed<TargetEnv>().oneOf(targetEnv.slice()).required(),
|
||||||
|
|
||||||
repoIds: yup.mixed().when("scope", {
|
repoIds: yup.mixed().when("scope", {
|
||||||
@@ -98,7 +99,6 @@ type FormData = yup.InferType<typeof schema>;
|
|||||||
export default function GitHubCreateIntegrationPage() {
|
export default function GitHubCreateIntegrationPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { mutateAsync } = useCreateIntegration();
|
const { mutateAsync } = useCreateIntegration();
|
||||||
|
|
||||||
|
|
||||||
const integrationAuthId =
|
const integrationAuthId =
|
||||||
(queryString.parse(router.asPath.split("?")[1]).integrationAuthId as string) ?? "";
|
(queryString.parse(router.asPath.split("?")[1]).integrationAuthId as string) ?? "";
|
||||||
@@ -120,7 +120,8 @@ export default function GitHubCreateIntegrationPage() {
|
|||||||
defaultValues: {
|
defaultValues: {
|
||||||
secretPath: "/",
|
secretPath: "/",
|
||||||
scope: "github-repo",
|
scope: "github-repo",
|
||||||
repoIds: []
|
repoIds: [],
|
||||||
|
shouldEnableDelete: false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -177,7 +178,8 @@ export default function GitHubCreateIntegrationPage() {
|
|||||||
app: targetApp.name, // repo name
|
app: targetApp.name, // repo name
|
||||||
owner: targetApp.owner, // repo owner
|
owner: targetApp.owner, // repo owner
|
||||||
metadata: {
|
metadata: {
|
||||||
secretSuffix: data.secretSuffix
|
secretSuffix: data.secretSuffix,
|
||||||
|
shouldEnableDelete: data.shouldEnableDelete
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -194,7 +196,8 @@ export default function GitHubCreateIntegrationPage() {
|
|||||||
scope: data.scope,
|
scope: data.scope,
|
||||||
owner: integrationAuthOrgs?.find((e) => e.orgId === data.orgId)?.name,
|
owner: integrationAuthOrgs?.find((e) => e.orgId === data.orgId)?.name,
|
||||||
metadata: {
|
metadata: {
|
||||||
secretSuffix: data.secretSuffix
|
secretSuffix: data.secretSuffix,
|
||||||
|
shouldEnableDelete: data.shouldEnableDelete
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
@@ -211,7 +214,8 @@ export default function GitHubCreateIntegrationPage() {
|
|||||||
owner: repoOwner,
|
owner: repoOwner,
|
||||||
targetEnvironmentId: data.envId,
|
targetEnvironmentId: data.envId,
|
||||||
metadata: {
|
metadata: {
|
||||||
secretSuffix: data.secretSuffix
|
secretSuffix: data.secretSuffix,
|
||||||
|
shouldEnableDelete: data.shouldEnableDelete
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
@@ -546,6 +550,21 @@ export default function GitHubCreateIntegrationPage() {
|
|||||||
animate={{ opacity: 1, translateX: 0 }}
|
animate={{ opacity: 1, translateX: 0 }}
|
||||||
exit={{ opacity: 0, translateX: 30 }}
|
exit={{ opacity: 0, translateX: 30 }}
|
||||||
>
|
>
|
||||||
|
<div className="ml-1 mb-5">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="shouldEnableDelete"
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
|
<Switch
|
||||||
|
id="delete-github-option"
|
||||||
|
onCheckedChange={(isChecked) => onChange(isChecked)}
|
||||||
|
isChecked={value}
|
||||||
|
>
|
||||||
|
Delete secrets in Github that are not in Infisical
|
||||||
|
</Switch>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="secretSuffix"
|
name="secretSuffix"
|
||||||
|
@@ -1,92 +0,0 @@
|
|||||||
import { Controller, useForm } from "react-hook-form";
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
|
||||||
import { Button, FormControl, Input } from "@app/components/v2";
|
|
||||||
import { useProjectPermission, useWorkspace } from "@app/context";
|
|
||||||
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
|
|
||||||
import { useUpdateWorkspaceVersionLimit } from "@app/hooks/api/workspace/queries";
|
|
||||||
|
|
||||||
const formSchema = z.object({
|
|
||||||
pitVersionLimit: z.coerce.number().min(1).max(100)
|
|
||||||
});
|
|
||||||
|
|
||||||
type TForm = z.infer<typeof formSchema>;
|
|
||||||
|
|
||||||
export const PointInTimeVersionLimitSection = () => {
|
|
||||||
const { mutateAsync: updatePitVersion } = useUpdateWorkspaceVersionLimit();
|
|
||||||
|
|
||||||
const { currentWorkspace } = useWorkspace();
|
|
||||||
const { membership } = useProjectPermission();
|
|
||||||
|
|
||||||
const {
|
|
||||||
control,
|
|
||||||
formState: { isSubmitting, isDirty },
|
|
||||||
handleSubmit
|
|
||||||
} = useForm<TForm>({
|
|
||||||
resolver: zodResolver(formSchema),
|
|
||||||
values: {
|
|
||||||
pitVersionLimit: currentWorkspace?.pitVersionLimit || 10
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!currentWorkspace) return null;
|
|
||||||
|
|
||||||
const handleVersionLimitSubmit = async ({ pitVersionLimit }: TForm) => {
|
|
||||||
try {
|
|
||||||
await updatePitVersion({
|
|
||||||
pitVersionLimit,
|
|
||||||
projectSlug: currentWorkspace.slug
|
|
||||||
});
|
|
||||||
|
|
||||||
createNotification({
|
|
||||||
text: "Successfully updated version limit",
|
|
||||||
type: "success"
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
createNotification({
|
|
||||||
text: "Failed updating project's version limit",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const isAdmin = membership.roles.includes(ProjectMembershipRole.Admin);
|
|
||||||
return (
|
|
||||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
|
||||||
<div className="flex w-full items-center justify-between">
|
|
||||||
<p className="text-xl font-semibold">Version Retention</p>
|
|
||||||
</div>
|
|
||||||
<p className="mb-4 mt-2 max-w-2xl text-sm text-gray-400">
|
|
||||||
This defines the maximum number of recent secret versions to keep per folder. Excess versions will be removed at midnight (UTC) each day.
|
|
||||||
</p>
|
|
||||||
<form onSubmit={handleSubmit(handleVersionLimitSubmit)} autoComplete="off">
|
|
||||||
<div className="max-w-xs">
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
defaultValue={0}
|
|
||||||
name="pitVersionLimit"
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
isError={Boolean(error)}
|
|
||||||
errorText={error?.message}
|
|
||||||
label="Recent versions to keep"
|
|
||||||
>
|
|
||||||
<Input {...field} type="number" min={1} step={1} isDisabled={!isAdmin} />
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
colorSchema="secondary"
|
|
||||||
type="submit"
|
|
||||||
isLoading={isSubmitting}
|
|
||||||
disabled={!isAdmin || !isDirty}
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@@ -1 +0,0 @@
|
|||||||
export { PointInTimeVersionLimitSection } from "./PointInTimeVersionLimitSection";
|
|
@@ -3,7 +3,6 @@ import { BackfillSecretReferenceSecretion } from "../BackfillSecretReferenceSect
|
|||||||
import { DeleteProjectSection } from "../DeleteProjectSection";
|
import { DeleteProjectSection } from "../DeleteProjectSection";
|
||||||
import { E2EESection } from "../E2EESection";
|
import { E2EESection } from "../E2EESection";
|
||||||
import { EnvironmentSection } from "../EnvironmentSection";
|
import { EnvironmentSection } from "../EnvironmentSection";
|
||||||
import { PointInTimeVersionLimitSection } from "../PointInTimeVersionLimitSection";
|
|
||||||
import { ProjectNameChangeSection } from "../ProjectNameChangeSection";
|
import { ProjectNameChangeSection } from "../ProjectNameChangeSection";
|
||||||
import { SecretTagsSection } from "../SecretTagsSection";
|
import { SecretTagsSection } from "../SecretTagsSection";
|
||||||
|
|
||||||
@@ -15,7 +14,6 @@ export const ProjectGeneralTab = () => {
|
|||||||
<SecretTagsSection />
|
<SecretTagsSection />
|
||||||
<AutoCapitalizationSection />
|
<AutoCapitalizationSection />
|
||||||
<E2EESection />
|
<E2EESection />
|
||||||
<PointInTimeVersionLimitSection />
|
|
||||||
<BackfillSecretReferenceSecretion />
|
<BackfillSecretReferenceSecretion />
|
||||||
<DeleteProjectSection />
|
<DeleteProjectSection />
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user