mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat: gcp secret sync management
This commit is contained in:
@@ -1,13 +1,18 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
import {
|
import {
|
||||||
CreateGcpConnectionSchema,
|
CreateGcpConnectionSchema,
|
||||||
SanitizedGcpConnectionSchema,
|
SanitizedGcpConnectionSchema,
|
||||||
UpdateGcpConnectionSchema
|
UpdateGcpConnectionSchema
|
||||||
} from "@app/services/app-connection/gcp";
|
} from "@app/services/app-connection/gcp";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
||||||
|
|
||||||
export const registerGcpConnectionRouter = async (server: FastifyZodProvider) =>
|
export const registerGcpConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
registerAppConnectionEndpoints({
|
registerAppConnectionEndpoints({
|
||||||
app: AppConnection.GCP,
|
app: AppConnection.GCP,
|
||||||
server,
|
server,
|
||||||
@@ -15,3 +20,29 @@ export const registerGcpConnectionRouter = async (server: FastifyZodProvider) =>
|
|||||||
createSchema: CreateGcpConnectionSchema,
|
createSchema: CreateGcpConnectionSchema,
|
||||||
updateSchema: UpdateGcpConnectionSchema
|
updateSchema: UpdateGcpConnectionSchema
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// The below endpoints are not exposed and for Infisical App use
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: `/:connectionId/secret-manager-projects`,
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
connectionId: z.string().uuid()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({ id: z.string(), name: z.string() }).array()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { connectionId } = req.params;
|
||||||
|
|
||||||
|
const projects = await server.services.appConnection.gcp.listSecretManagerProjects(connectionId, req.permission);
|
||||||
|
|
||||||
|
return projects;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { CreateGcpSyncSchema, GcpSyncSchema, UpdateGcpSyncSchema } from "@app/services/secret-sync/gcp";
|
||||||
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
|
||||||
|
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
|
||||||
|
|
||||||
|
export const registerGcpSyncRouter = async (server: FastifyZodProvider) =>
|
||||||
|
registerSyncSecretsEndpoints({
|
||||||
|
destination: SecretSync.GCP,
|
||||||
|
server,
|
||||||
|
responseSchema: GcpSyncSchema,
|
||||||
|
createSchema: CreateGcpSyncSchema,
|
||||||
|
updateSchema: UpdateGcpSyncSchema
|
||||||
|
});
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
|
||||||
import { registerAwsParameterStoreSyncRouter } from "./aws-parameter-store-sync-router";
|
import { registerAwsParameterStoreSyncRouter } from "./aws-parameter-store-sync-router";
|
||||||
|
import { registerGcpSyncRouter } from "./gcp-sync-router";
|
||||||
import { registerGitHubSyncRouter } from "./github-sync-router";
|
import { registerGitHubSyncRouter } from "./github-sync-router";
|
||||||
|
|
||||||
export * from "./secret-sync-router";
|
export * from "./secret-sync-router";
|
||||||
|
|
||||||
export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record<SecretSync, (server: FastifyZodProvider) => Promise<void>> = {
|
export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record<SecretSync, (server: FastifyZodProvider) => Promise<void>> = {
|
||||||
[SecretSync.AWSParameterStore]: registerAwsParameterStoreSyncRouter,
|
[SecretSync.AWSParameterStore]: registerAwsParameterStoreSyncRouter,
|
||||||
[SecretSync.GitHub]: registerGitHubSyncRouter
|
[SecretSync.GitHub]: registerGitHubSyncRouter,
|
||||||
|
[SecretSync.GCP]: registerGcpSyncRouter
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,13 +9,19 @@ import {
|
|||||||
AwsParameterStoreSyncListItemSchema,
|
AwsParameterStoreSyncListItemSchema,
|
||||||
AwsParameterStoreSyncSchema
|
AwsParameterStoreSyncSchema
|
||||||
} from "@app/services/secret-sync/aws-parameter-store";
|
} from "@app/services/secret-sync/aws-parameter-store";
|
||||||
|
import { GcpSyncListItemSchema, GcpSyncSchema } from "@app/services/secret-sync/gcp";
|
||||||
import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret-sync/github";
|
import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret-sync/github";
|
||||||
|
|
||||||
const SecretSyncSchema = z.discriminatedUnion("destination", [AwsParameterStoreSyncSchema, GitHubSyncSchema]);
|
const SecretSyncSchema = z.discriminatedUnion("destination", [
|
||||||
|
AwsParameterStoreSyncSchema,
|
||||||
|
GitHubSyncSchema,
|
||||||
|
GcpSyncSchema
|
||||||
|
]);
|
||||||
|
|
||||||
const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
||||||
AwsParameterStoreSyncListItemSchema,
|
AwsParameterStoreSyncListItemSchema,
|
||||||
GitHubSyncListItemSchema
|
GitHubSyncListItemSchema,
|
||||||
|
GcpSyncListItemSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const registerSecretSyncRouter = async (server: FastifyZodProvider) => {
|
export const registerSecretSyncRouter = async (server: FastifyZodProvider) => {
|
||||||
|
|||||||
@@ -2,5 +2,6 @@ import { AppConnection } from "./app-connection-enums";
|
|||||||
|
|
||||||
export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
|
export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
|
||||||
[AppConnection.AWS]: "AWS",
|
[AppConnection.AWS]: "AWS",
|
||||||
[AppConnection.GitHub]: "GitHub"
|
[AppConnection.GitHub]: "GitHub",
|
||||||
|
[AppConnection.GCP]: "GCP"
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
|||||||
|
|
||||||
import { TAppConnectionDALFactory } from "./app-connection-dal";
|
import { TAppConnectionDALFactory } from "./app-connection-dal";
|
||||||
import { ValidateGcpConnectionCredentialsSchema } from "./gcp";
|
import { ValidateGcpConnectionCredentialsSchema } from "./gcp";
|
||||||
|
import { gcpConnectionService } from "./gcp/gcp-connection-service";
|
||||||
|
|
||||||
export type TAppConnectionServiceFactoryDep = {
|
export type TAppConnectionServiceFactoryDep = {
|
||||||
appConnectionDAL: TAppConnectionDALFactory;
|
appConnectionDAL: TAppConnectionDALFactory;
|
||||||
@@ -384,6 +385,7 @@ export const appConnectionServiceFactory = ({
|
|||||||
deleteAppConnection,
|
deleteAppConnection,
|
||||||
connectAppConnectionById,
|
connectAppConnectionById,
|
||||||
listAvailableAppConnectionsForUser,
|
listAvailableAppConnectionsForUser,
|
||||||
github: githubConnectionService(connectAppConnectionById)
|
github: githubConnectionService(connectAppConnectionById),
|
||||||
|
gcp: gcpConnectionService(connectAppConnectionById)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,12 +2,20 @@ import { gaxios, Impersonated, JWT } from "google-auth-library";
|
|||||||
import { GetAccessTokenResponse } from "google-auth-library/build/src/auth/oauth2client";
|
import { GetAccessTokenResponse } from "google-auth-library/build/src/auth/oauth2client";
|
||||||
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { request } from "@app/lib/config/request";
|
||||||
import { BadRequestError, InternalServerError } from "@app/lib/errors";
|
import { BadRequestError, InternalServerError } from "@app/lib/errors";
|
||||||
|
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
||||||
|
|
||||||
import { AppConnection } from "../app-connection-enums";
|
import { AppConnection } from "../app-connection-enums";
|
||||||
import { getAppConnectionMethodName } from "../app-connection-fns";
|
import { getAppConnectionMethodName } from "../app-connection-fns";
|
||||||
import { GcpConnectionMethod } from "./gcp-connection-enums";
|
import { GcpConnectionMethod } from "./gcp-connection-enums";
|
||||||
import { TGcpConnectionConfig } from "./gcp-connection-types";
|
import {
|
||||||
|
GCPApp,
|
||||||
|
GCPGetProjectsRes,
|
||||||
|
GCPGetServiceRes,
|
||||||
|
TGcpConnection,
|
||||||
|
TGcpConnectionConfig
|
||||||
|
} from "./gcp-connection-types";
|
||||||
|
|
||||||
export const getGcpAppConnectionListItem = () => {
|
export const getGcpAppConnectionListItem = () => {
|
||||||
return {
|
return {
|
||||||
@@ -17,9 +25,8 @@ export const getGcpAppConnectionListItem = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const validateGcpConnectionCredentials = async (appConnection: TGcpConnectionConfig) => {
|
export const getAuthToken = async (appConnection: TGcpConnectionConfig) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
|
|
||||||
if (!appCfg.INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL) {
|
if (!appCfg.INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL) {
|
||||||
throw new InternalServerError({
|
throw new InternalServerError({
|
||||||
message: `Environment variables have not been configured for GCP ${getAppConnectionMethodName(
|
message: `Environment variables have not been configured for GCP ${getAppConnectionMethodName(
|
||||||
@@ -67,5 +74,79 @@ export const validateGcpConnectionCredentials = async (appConnection: TGcpConnec
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return tokenResponse.token;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getGcpSecretManagerProjects = async (appConnection: TGcpConnection) => {
|
||||||
|
const accessToken = await getAuthToken(appConnection);
|
||||||
|
|
||||||
|
let gcpApps: GCPApp[] = [];
|
||||||
|
|
||||||
|
const pageSize = 100;
|
||||||
|
let pageToken: string | undefined;
|
||||||
|
let hasMorePages = true;
|
||||||
|
|
||||||
|
const projects: {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
}[] = [];
|
||||||
|
|
||||||
|
while (hasMorePages) {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
pageSize: String(pageSize),
|
||||||
|
...(pageToken ? { pageToken } : {})
|
||||||
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const { data } = await request.get<GCPGetProjectsRes>(`${IntegrationUrls.GCP_API_URL}/v1/projects`, {
|
||||||
|
params,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Accept-Encoding": "application/json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
gcpApps = gcpApps.concat(data.projects);
|
||||||
|
|
||||||
|
if (!data.nextPageToken) {
|
||||||
|
hasMorePages = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pageToken = data.nextPageToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
for await (const gcpApp of gcpApps) {
|
||||||
|
try {
|
||||||
|
const res = (
|
||||||
|
await request.get<GCPGetServiceRes>(
|
||||||
|
`${IntegrationUrls.GCP_SERVICE_USAGE_URL}/v1/projects/${gcpApp.projectId}/services/${IntegrationUrls.GCP_SECRET_MANAGER_SERVICE_NAME}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
"Accept-Encoding": "application/json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
).data;
|
||||||
|
|
||||||
|
if (res.state === "ENABLED") {
|
||||||
|
projects.push({
|
||||||
|
name: gcpApp.name,
|
||||||
|
id: gcpApp.projectId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return projects;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validateGcpConnectionCredentials = async (appConnection: TGcpConnectionConfig) => {
|
||||||
|
await getAuthToken(appConnection);
|
||||||
|
|
||||||
return appConnection.credentials;
|
return appConnection.credentials;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { AppConnection } from "../app-connection-enums";
|
||||||
|
import { getGcpSecretManagerProjects } from "./gcp-connection-fns";
|
||||||
|
import { TGcpConnection } from "./gcp-connection-types";
|
||||||
|
|
||||||
|
type TGetAppConnectionFunc = (
|
||||||
|
app: AppConnection,
|
||||||
|
connectionId: string,
|
||||||
|
actor: OrgServiceActor
|
||||||
|
) => Promise<TGcpConnection>;
|
||||||
|
|
||||||
|
export const gcpConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
|
||||||
|
const listSecretManagerProjects = async (connectionId: string, actor: OrgServiceActor) => {
|
||||||
|
const appConnection = await getAppConnection(AppConnection.GCP, connectionId, actor);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const projects = await getGcpSecretManagerProjects(appConnection);
|
||||||
|
|
||||||
|
return projects;
|
||||||
|
} catch (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
listSecretManagerProjects
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -20,3 +20,26 @@ export type TValidateGcpConnectionCredentials = typeof ValidateGcpConnectionCred
|
|||||||
export type TGcpConnectionConfig = DiscriminativePick<TGcpConnectionInput, "method" | "app" | "credentials"> & {
|
export type TGcpConnectionConfig = DiscriminativePick<TGcpConnectionInput, "method" | "app" | "credentials"> & {
|
||||||
orgId: string;
|
orgId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface GCPApp {
|
||||||
|
projectNumber: string;
|
||||||
|
projectId: string;
|
||||||
|
lifecycleState: "ACTIVE" | "LIFECYCLE_STATE_UNSPECIFIED" | "DELETE_REQUESTED" | "DELETE_IN_PROGRESS";
|
||||||
|
name: string;
|
||||||
|
createTime: string;
|
||||||
|
parent: {
|
||||||
|
type: "organization" | "folder" | "project";
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GCPGetProjectsRes {
|
||||||
|
projects: GCPApp[];
|
||||||
|
nextPageToken?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GCPGetServiceRes {
|
||||||
|
name: string;
|
||||||
|
parent: string;
|
||||||
|
state: "ENABLED" | "DISABLED" | "STATE_UNSPECIFIED";
|
||||||
|
}
|
||||||
|
|||||||
10
backend/src/services/secret-sync/gcp/gcp-sync-constants.ts
Normal file
10
backend/src/services/secret-sync/gcp/gcp-sync-constants.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
import { TSecretSyncListItem } from "@app/services/secret-sync/secret-sync-types";
|
||||||
|
|
||||||
|
export const GCP_SYNC_LIST_OPTION: TSecretSyncListItem = {
|
||||||
|
name: "GCP",
|
||||||
|
destination: SecretSync.GCP,
|
||||||
|
connection: AppConnection.GCP,
|
||||||
|
canImportSecrets: false
|
||||||
|
};
|
||||||
37
backend/src/services/secret-sync/gcp/gcp-sync-schemas.ts
Normal file
37
backend/src/services/secret-sync/gcp/gcp-sync-schemas.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import {
|
||||||
|
BaseSecretSyncSchema,
|
||||||
|
GenericCreateSecretSyncFieldsSchema,
|
||||||
|
GenericUpdateSecretSyncFieldsSchema
|
||||||
|
} from "@app/services/secret-sync/secret-sync-schemas";
|
||||||
|
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||||
|
|
||||||
|
import { SecretSync } from "../secret-sync-enums";
|
||||||
|
|
||||||
|
const GcpSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: false };
|
||||||
|
|
||||||
|
const GcpSyncDestinationConfigSchema = z.object({
|
||||||
|
projectId: z.string().min(1, "Project ID is required")
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GcpSyncSchema = BaseSecretSyncSchema(SecretSync.GCP, GcpSyncOptionsConfig).extend({
|
||||||
|
destination: z.literal(SecretSync.GCP),
|
||||||
|
destinationConfig: GcpSyncDestinationConfigSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateGcpSyncSchema = GenericCreateSecretSyncFieldsSchema(SecretSync.GCP, GcpSyncOptionsConfig).extend({
|
||||||
|
destinationConfig: GcpSyncDestinationConfigSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateGcpSyncSchema = GenericUpdateSecretSyncFieldsSchema(SecretSync.GCP, GcpSyncOptionsConfig).extend({
|
||||||
|
destinationConfig: GcpSyncDestinationConfigSchema.optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GcpSyncListItemSchema = z.object({
|
||||||
|
name: z.literal("GCP"),
|
||||||
|
connection: z.literal(AppConnection.GCP),
|
||||||
|
destination: z.literal(SecretSync.GCP),
|
||||||
|
canImportSecrets: z.literal(false)
|
||||||
|
});
|
||||||
15
backend/src/services/secret-sync/gcp/gcp-sync-types.ts
Normal file
15
backend/src/services/secret-sync/gcp/gcp-sync-types.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { TGcpConnection } from "@app/services/app-connection/gcp";
|
||||||
|
|
||||||
|
import { CreateGcpSyncSchema, GcpSyncListItemSchema, GcpSyncSchema } from "./gcp-sync-schemas";
|
||||||
|
|
||||||
|
export type TGcpSyncListItem = z.infer<typeof GcpSyncListItemSchema>;
|
||||||
|
|
||||||
|
export type TGcpSync = z.infer<typeof GcpSyncSchema>;
|
||||||
|
|
||||||
|
export type TGcpSyncInput = z.infer<typeof CreateGcpSyncSchema>;
|
||||||
|
|
||||||
|
export type TGcpSyncWithCredentials = TGcpSync & {
|
||||||
|
connection: TGcpConnection;
|
||||||
|
};
|
||||||
3
backend/src/services/secret-sync/gcp/index.ts
Normal file
3
backend/src/services/secret-sync/gcp/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from "./gcp-sync-constants";
|
||||||
|
export * from "./gcp-sync-schemas";
|
||||||
|
export * from "./gcp-sync-types";
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
export enum SecretSync {
|
export enum SecretSync {
|
||||||
AWSParameterStore = "aws-parameter-store",
|
AWSParameterStore = "aws-parameter-store",
|
||||||
GitHub = "github"
|
GitHub = "github",
|
||||||
|
GCP = "gcp"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SecretSyncInitialSyncBehavior {
|
export enum SecretSyncInitialSyncBehavior {
|
||||||
|
|||||||
@@ -13,9 +13,12 @@ import {
|
|||||||
TSecretSyncWithCredentials
|
TSecretSyncWithCredentials
|
||||||
} from "@app/services/secret-sync/secret-sync-types";
|
} from "@app/services/secret-sync/secret-sync-types";
|
||||||
|
|
||||||
|
import { GCP_SYNC_LIST_OPTION } from "./gcp";
|
||||||
|
|
||||||
const SECRET_SYNC_LIST_OPTIONS: Record<SecretSync, TSecretSyncListItem> = {
|
const SECRET_SYNC_LIST_OPTIONS: Record<SecretSync, TSecretSyncListItem> = {
|
||||||
[SecretSync.AWSParameterStore]: AWS_PARAMETER_STORE_SYNC_LIST_OPTION,
|
[SecretSync.AWSParameterStore]: AWS_PARAMETER_STORE_SYNC_LIST_OPTION,
|
||||||
[SecretSync.GitHub]: GITHUB_SYNC_LIST_OPTION
|
[SecretSync.GitHub]: GITHUB_SYNC_LIST_OPTION,
|
||||||
|
[SecretSync.GCP]: GCP_SYNC_LIST_OPTION
|
||||||
};
|
};
|
||||||
|
|
||||||
export const listSecretSyncOptions = () => {
|
export const listSecretSyncOptions = () => {
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
|||||||
|
|
||||||
export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
|
export const SECRET_SYNC_NAME_MAP: Record<SecretSync, string> = {
|
||||||
[SecretSync.AWSParameterStore]: "AWS Parameter Store",
|
[SecretSync.AWSParameterStore]: "AWS Parameter Store",
|
||||||
[SecretSync.GitHub]: "GitHub"
|
[SecretSync.GitHub]: "GitHub",
|
||||||
|
[SecretSync.GCP]: "GCP Secret Manager"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
||||||
[SecretSync.AWSParameterStore]: AppConnection.AWS,
|
[SecretSync.AWSParameterStore]: AppConnection.AWS,
|
||||||
[SecretSync.GitHub]: AppConnection.GitHub
|
[SecretSync.GitHub]: AppConnection.GitHub,
|
||||||
|
[SecretSync.GCP]: AppConnection.GCP
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,14 +17,18 @@ import {
|
|||||||
TAwsParameterStoreSyncListItem,
|
TAwsParameterStoreSyncListItem,
|
||||||
TAwsParameterStoreSyncWithCredentials
|
TAwsParameterStoreSyncWithCredentials
|
||||||
} from "./aws-parameter-store";
|
} from "./aws-parameter-store";
|
||||||
|
import { TGcpSync, TGcpSyncInput, TGcpSyncListItem, TGcpSyncWithCredentials } from "./gcp";
|
||||||
|
|
||||||
export type TSecretSync = TAwsParameterStoreSync | TGitHubSync;
|
export type TSecretSync = TAwsParameterStoreSync | TGitHubSync | TGcpSync;
|
||||||
|
|
||||||
export type TSecretSyncWithCredentials = TAwsParameterStoreSyncWithCredentials | TGitHubSyncWithCredentials;
|
export type TSecretSyncWithCredentials =
|
||||||
|
| TAwsParameterStoreSyncWithCredentials
|
||||||
|
| TGitHubSyncWithCredentials
|
||||||
|
| TGcpSyncWithCredentials;
|
||||||
|
|
||||||
export type TSecretSyncInput = TAwsParameterStoreSyncInput | TGitHubSyncInput;
|
export type TSecretSyncInput = TAwsParameterStoreSyncInput | TGitHubSyncInput | TGcpSyncInput;
|
||||||
|
|
||||||
export type TSecretSyncListItem = TAwsParameterStoreSyncListItem | TGitHubSyncListItem;
|
export type TSecretSyncListItem = TAwsParameterStoreSyncListItem | TGitHubSyncListItem | TGcpSyncListItem;
|
||||||
|
|
||||||
export type TSyncOptionsConfig = {
|
export type TSyncOptionsConfig = {
|
||||||
canImportSecrets: boolean;
|
canImportSecrets: boolean;
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||||
|
import { SingleValue } from "react-select";
|
||||||
|
|
||||||
|
import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField";
|
||||||
|
import { FilterableSelect, FormControl } from "@app/components/v2";
|
||||||
|
import { useGcpConnectionListProjects } from "@app/hooks/api/appConnections/gcp/queries";
|
||||||
|
import { TGitHubConnectionEnvironment } from "@app/hooks/api/appConnections/github";
|
||||||
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
|
||||||
|
import { TSecretSyncForm } from "../schemas";
|
||||||
|
|
||||||
|
export const GcpSyncFields = () => {
|
||||||
|
const { control, setValue } = useFormContext<TSecretSyncForm & { destination: SecretSync.GCP }>();
|
||||||
|
|
||||||
|
const connectionId = useWatch({ name: "connection.id", control });
|
||||||
|
|
||||||
|
const { data: projects, isPending } = useGcpConnectionListProjects(connectionId, {
|
||||||
|
enabled: Boolean(connectionId)
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SecretSyncConnectionField
|
||||||
|
onChange={() => {
|
||||||
|
setValue("destinationConfig.projectId", "");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
name="destinationConfig.projectId"
|
||||||
|
control={control}
|
||||||
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
|
<FormControl isError={Boolean(error)} errorText={error?.message} label="Project">
|
||||||
|
<FilterableSelect
|
||||||
|
menuPlacement="top"
|
||||||
|
isLoading={isPending && Boolean(connectionId)}
|
||||||
|
isDisabled={!connectionId}
|
||||||
|
value={projects?.find((project) => project.id === value) ?? null}
|
||||||
|
onChange={(option) =>
|
||||||
|
onChange((option as SingleValue<TGitHubConnectionEnvironment>)?.id ?? null)
|
||||||
|
}
|
||||||
|
options={projects}
|
||||||
|
placeholder="Select a GCP project..."
|
||||||
|
getOptionLabel={(option) => option.name}
|
||||||
|
getOptionValue={(option) => option.id.toString()}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -4,6 +4,7 @@ import { SecretSync } from "@app/hooks/api/secretSyncs";
|
|||||||
|
|
||||||
import { TSecretSyncForm } from "../schemas";
|
import { TSecretSyncForm } from "../schemas";
|
||||||
import { AwsParameterStoreSyncFields } from "./AwsParameterStoreSyncFields";
|
import { AwsParameterStoreSyncFields } from "./AwsParameterStoreSyncFields";
|
||||||
|
import { GcpSyncFields } from "./GcpSyncFields";
|
||||||
import { GitHubSyncFields } from "./GitHubSyncFields";
|
import { GitHubSyncFields } from "./GitHubSyncFields";
|
||||||
|
|
||||||
export const SecretSyncDestinationFields = () => {
|
export const SecretSyncDestinationFields = () => {
|
||||||
@@ -16,6 +17,8 @@ export const SecretSyncDestinationFields = () => {
|
|||||||
return <AwsParameterStoreSyncFields />;
|
return <AwsParameterStoreSyncFields />;
|
||||||
case SecretSync.GitHub:
|
case SecretSync.GitHub:
|
||||||
return <GitHubSyncFields />;
|
return <GitHubSyncFields />;
|
||||||
|
case SecretSync.GCP:
|
||||||
|
return <GcpSyncFields />;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled Destination Config Field: ${destination}`);
|
throw new Error(`Unhandled Destination Config Field: ${destination}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { useFormContext } from "react-hook-form";
|
||||||
|
|
||||||
|
import { SecretSyncLabel } from "@app/components/secret-syncs";
|
||||||
|
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
|
||||||
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
|
||||||
|
export const GcpSyncReviewFields = () => {
|
||||||
|
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.GCP }>();
|
||||||
|
const projectId = watch("destinationConfig.projectId");
|
||||||
|
|
||||||
|
return <SecretSyncLabel label="Project ID">{projectId}</SecretSyncLabel>;
|
||||||
|
};
|
||||||
@@ -8,6 +8,7 @@ import { SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP, SECRET_SYNC_MAP } from "@app/hel
|
|||||||
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
|
||||||
import { AwsParameterStoreSyncReviewFields } from "./AwsParameterStoreSyncReviewFields";
|
import { AwsParameterStoreSyncReviewFields } from "./AwsParameterStoreSyncReviewFields";
|
||||||
|
import { GcpSyncReviewFields } from "./GcpSyncReviewFIelds";
|
||||||
import { GitHubSyncReviewFields } from "./GitHubSyncReviewFields";
|
import { GitHubSyncReviewFields } from "./GitHubSyncReviewFields";
|
||||||
|
|
||||||
export const SecretSyncReviewFields = () => {
|
export const SecretSyncReviewFields = () => {
|
||||||
@@ -38,6 +39,9 @@ export const SecretSyncReviewFields = () => {
|
|||||||
case SecretSync.GitHub:
|
case SecretSync.GitHub:
|
||||||
DestinationFieldsComponent = <GitHubSyncReviewFields />;
|
DestinationFieldsComponent = <GitHubSyncReviewFields />;
|
||||||
break;
|
break;
|
||||||
|
case SecretSync.GCP:
|
||||||
|
DestinationFieldsComponent = <GcpSyncReviewFields />;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled Destination Review Fields: ${destination}`);
|
throw new Error(`Unhandled Destination Review Fields: ${destination}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
|
||||||
|
export const GcpSyncDestinationSchema = z.object({
|
||||||
|
destination: z.literal(SecretSync.GCP),
|
||||||
|
destinationConfig: z.object({
|
||||||
|
projectId: z.string().min(1, "Project ID required")
|
||||||
|
})
|
||||||
|
});
|
||||||
@@ -5,6 +5,7 @@ import { SecretSyncInitialSyncBehavior } from "@app/hooks/api/secretSyncs";
|
|||||||
import { slugSchema } from "@app/lib/schemas";
|
import { slugSchema } from "@app/lib/schemas";
|
||||||
|
|
||||||
import { AwsParameterStoreSyncDestinationSchema } from "./aws-parameter-store-sync-destination-schema";
|
import { AwsParameterStoreSyncDestinationSchema } from "./aws-parameter-store-sync-destination-schema";
|
||||||
|
import { GcpSyncDestinationSchema } from "./gcp-sync-destination-schema";
|
||||||
|
|
||||||
const BaseSecretSyncSchema = z.object({
|
const BaseSecretSyncSchema = z.object({
|
||||||
name: slugSchema({ field: "Name" }),
|
name: slugSchema({ field: "Name" }),
|
||||||
@@ -31,7 +32,8 @@ const BaseSecretSyncSchema = z.object({
|
|||||||
|
|
||||||
const SecretSyncUnionSchema = z.discriminatedUnion("destination", [
|
const SecretSyncUnionSchema = z.discriminatedUnion("destination", [
|
||||||
AwsParameterStoreSyncDestinationSchema,
|
AwsParameterStoreSyncDestinationSchema,
|
||||||
GitHubSyncDestinationSchema
|
GitHubSyncDestinationSchema,
|
||||||
|
GcpSyncDestinationSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const SecretSyncFormSchema = SecretSyncUnionSchema.and(BaseSecretSyncSchema);
|
export const SecretSyncFormSchema = SecretSyncUnionSchema.and(BaseSecretSyncSchema);
|
||||||
|
|||||||
@@ -7,12 +7,14 @@ import {
|
|||||||
|
|
||||||
export const SECRET_SYNC_MAP: Record<SecretSync, { name: string; image: string }> = {
|
export const SECRET_SYNC_MAP: Record<SecretSync, { name: string; image: string }> = {
|
||||||
[SecretSync.AWSParameterStore]: { name: "Parameter Store", image: "Amazon Web Services.png" },
|
[SecretSync.AWSParameterStore]: { name: "Parameter Store", image: "Amazon Web Services.png" },
|
||||||
[SecretSync.GitHub]: { name: "GitHub", image: "GitHub.png" }
|
[SecretSync.GitHub]: { name: "GitHub", image: "GitHub.png" },
|
||||||
|
[SecretSync.GCP]: { name: "GCP Secret Manager", image: "Google Cloud Platform.png" }
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
export const SECRET_SYNC_CONNECTION_MAP: Record<SecretSync, AppConnection> = {
|
||||||
[SecretSync.AWSParameterStore]: AppConnection.AWS,
|
[SecretSync.AWSParameterStore]: AppConnection.AWS,
|
||||||
[SecretSync.GitHub]: AppConnection.GitHub
|
[SecretSync.GitHub]: AppConnection.GitHub,
|
||||||
|
[SecretSync.GCP]: AppConnection.GCP
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP: Record<
|
export const SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP: Record<
|
||||||
|
|||||||
37
frontend/src/hooks/api/appConnections/gcp/queries.tsx
Normal file
37
frontend/src/hooks/api/appConnections/gcp/queries.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
|
import { appConnectionKeys } from "../queries";
|
||||||
|
import { TGcpProject } from "./types";
|
||||||
|
|
||||||
|
const gcpConnectionKeys = {
|
||||||
|
all: [...appConnectionKeys.all, "gcp"] as const,
|
||||||
|
listProjects: (connectionId: string) =>
|
||||||
|
[...gcpConnectionKeys.all, "projects", connectionId] as const
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGcpConnectionListProjects = (
|
||||||
|
connectionId: string,
|
||||||
|
options?: Omit<
|
||||||
|
UseQueryOptions<
|
||||||
|
TGcpProject[],
|
||||||
|
unknown,
|
||||||
|
TGcpProject[],
|
||||||
|
ReturnType<typeof gcpConnectionKeys.listProjects>
|
||||||
|
>,
|
||||||
|
"queryKey" | "queryFn"
|
||||||
|
>
|
||||||
|
) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: gcpConnectionKeys.listProjects(connectionId),
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await apiRequest.get<TGcpProject[]>(
|
||||||
|
`/api/v1/app-connections/gcp/${connectionId}/secret-manager-projects`
|
||||||
|
);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
};
|
||||||
4
frontend/src/hooks/api/appConnections/gcp/types.ts
Normal file
4
frontend/src/hooks/api/appConnections/gcp/types.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export type TGcpProject = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
export enum SecretSync {
|
export enum SecretSync {
|
||||||
AWSParameterStore = "aws-parameter-store",
|
AWSParameterStore = "aws-parameter-store",
|
||||||
GitHub = "github"
|
GitHub = "github",
|
||||||
|
GCP = "gcp"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SecretSyncStatus {
|
export enum SecretSyncStatus {
|
||||||
|
|||||||
15
frontend/src/hooks/api/secretSyncs/types/gcp-sync.ts
Normal file
15
frontend/src/hooks/api/secretSyncs/types/gcp-sync.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||||
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
import { TRootSecretSync } from "@app/hooks/api/secretSyncs/types/root-sync";
|
||||||
|
|
||||||
|
export type TGcpSync = TRootSecretSync & {
|
||||||
|
destination: SecretSync.GCP;
|
||||||
|
destinationConfig: {
|
||||||
|
projectId: string;
|
||||||
|
};
|
||||||
|
connection: {
|
||||||
|
app: AppConnection.GCP;
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -3,13 +3,15 @@ import { TAwsParameterStoreSync } from "@app/hooks/api/secretSyncs/types/aws-par
|
|||||||
import { TGitHubSync } from "@app/hooks/api/secretSyncs/types/github-sync";
|
import { TGitHubSync } from "@app/hooks/api/secretSyncs/types/github-sync";
|
||||||
import { DiscriminativePick } from "@app/types";
|
import { DiscriminativePick } from "@app/types";
|
||||||
|
|
||||||
|
import { TGcpSync } from "./gcp-sync";
|
||||||
|
|
||||||
export type TSecretSyncOption = {
|
export type TSecretSyncOption = {
|
||||||
name: string;
|
name: string;
|
||||||
destination: SecretSync;
|
destination: SecretSync;
|
||||||
canImportSecrets: boolean;
|
canImportSecrets: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretSync = TAwsParameterStoreSync | TGitHubSync;
|
export type TSecretSync = TAwsParameterStoreSync | TGitHubSync | TGcpSync;
|
||||||
|
|
||||||
export type TListSecretSyncs = { secretSyncs: TSecretSync[] };
|
export type TListSecretSyncs = { secretSyncs: TSecretSync[] };
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { TGcpSync } from "@app/hooks/api/secretSyncs/types/gcp-sync";
|
||||||
|
|
||||||
|
import { getSecretSyncDestinationColValues } from "../helpers";
|
||||||
|
import { SecretSyncTableCell } from "../SecretSyncTableCell";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
secretSync: TGcpSync;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GcpSyncDestinationCol = ({ secretSync }: Props) => {
|
||||||
|
const { primaryText, secondaryText } = getSecretSyncDestinationColValues(secretSync);
|
||||||
|
|
||||||
|
return <SecretSyncTableCell primaryText={primaryText} secondaryText={secondaryText} />;
|
||||||
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { SecretSync, TSecretSync } from "@app/hooks/api/secretSyncs";
|
import { SecretSync, TSecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
|
||||||
import { AwsParameterStoreSyncDestinationCol } from "./AwsParameterStoreSyncDestinationCol";
|
import { AwsParameterStoreSyncDestinationCol } from "./AwsParameterStoreSyncDestinationCol";
|
||||||
|
import { GcpSyncDestinationCol } from "./GcpSyncDestinationCol";
|
||||||
import { GitHubSyncDestinationCol } from "./GitHubSyncDestinationCol";
|
import { GitHubSyncDestinationCol } from "./GitHubSyncDestinationCol";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -13,6 +14,8 @@ export const SecretSyncDestinationCol = ({ secretSync }: Props) => {
|
|||||||
return <AwsParameterStoreSyncDestinationCol secretSync={secretSync} />;
|
return <AwsParameterStoreSyncDestinationCol secretSync={secretSync} />;
|
||||||
case SecretSync.GitHub:
|
case SecretSync.GitHub:
|
||||||
return <GitHubSyncDestinationCol secretSync={secretSync} />;
|
return <GitHubSyncDestinationCol secretSync={secretSync} />;
|
||||||
|
case SecretSync.GCP:
|
||||||
|
return <GcpSyncDestinationCol secretSync={secretSync} />;
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unhandled Secret Sync Destination Col: ${(secretSync as TSecretSync).destination}`
|
`Unhandled Secret Sync Destination Col: ${(secretSync as TSecretSync).destination}`
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ export const getSecretSyncDestinationColValues = (secretSync: TSecretSync) => {
|
|||||||
throw new Error(`Unhandled GitHub Scope Destination Col Values ${destination}`);
|
throw new Error(`Unhandled GitHub Scope Destination Col Values ${destination}`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case SecretSync.GCP:
|
||||||
|
primaryText = destinationConfig.projectId;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled Destination Col Values ${destination}`);
|
throw new Error(`Unhandled Destination Col Values ${destination}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { SecretSyncLabel } from "@app/components/secret-syncs";
|
||||||
|
import { TGcpSync } from "@app/hooks/api/secretSyncs/types/gcp-sync";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
secretSync: TGcpSync;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GcpSyncDestinationSection = ({ secretSync }: Props) => {
|
||||||
|
const {
|
||||||
|
destinationConfig: { projectId }
|
||||||
|
} = secretSync;
|
||||||
|
|
||||||
|
return <SecretSyncLabel label="Project ID">{projectId}</SecretSyncLabel>;
|
||||||
|
};
|
||||||
@@ -12,6 +12,8 @@ import { SecretSync, TSecretSync } from "@app/hooks/api/secretSyncs";
|
|||||||
import { AwsParameterStoreSyncDestinationSection } from "@app/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/AwsParameterStoreSyncDestinationSection";
|
import { AwsParameterStoreSyncDestinationSection } from "@app/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/AwsParameterStoreSyncDestinationSection";
|
||||||
import { GitHubSyncDestinationSection } from "@app/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/GitHubSyncDestinationSection";
|
import { GitHubSyncDestinationSection } from "@app/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncDestinationSection/GitHubSyncDestinationSection";
|
||||||
|
|
||||||
|
import { GcpSyncDestinationSection } from "./GcpSyncDestinationSection";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
secretSync: TSecretSync;
|
secretSync: TSecretSync;
|
||||||
onEditDestination: VoidFunction;
|
onEditDestination: VoidFunction;
|
||||||
@@ -30,6 +32,9 @@ export const SecretSyncDestinationSection = ({ secretSync, onEditDestination }:
|
|||||||
case SecretSync.GitHub:
|
case SecretSync.GitHub:
|
||||||
DestinationComponents = <GitHubSyncDestinationSection secretSync={secretSync} />;
|
DestinationComponents = <GitHubSyncDestinationSection secretSync={secretSync} />;
|
||||||
break;
|
break;
|
||||||
|
case SecretSync.GCP:
|
||||||
|
DestinationComponents = <GcpSyncDestinationSection secretSync={secretSync} />;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled Destination Section components: ${destination}`);
|
throw new Error(`Unhandled Destination Section components: ${destination}`);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user