mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-03 02:25:48 +00:00
feat(secret-sync): rework GitLab secret-sync to add group variables
This commit is contained in:
Generated
+67
@@ -30,6 +30,7 @@
|
|||||||
"@fastify/static": "^7.0.4",
|
"@fastify/static": "^7.0.4",
|
||||||
"@fastify/swagger": "^8.14.0",
|
"@fastify/swagger": "^8.14.0",
|
||||||
"@fastify/swagger-ui": "^2.1.0",
|
"@fastify/swagger-ui": "^2.1.0",
|
||||||
|
"@gitbeaker/rest": "^42.5.0",
|
||||||
"@google-cloud/kms": "^4.5.0",
|
"@google-cloud/kms": "^4.5.0",
|
||||||
"@infisical/quic": "^1.0.8",
|
"@infisical/quic": "^1.0.8",
|
||||||
"@node-saml/passport-saml": "^5.0.1",
|
"@node-saml/passport-saml": "^5.0.1",
|
||||||
@@ -7807,6 +7808,48 @@
|
|||||||
"p-limit": "^3.1.0"
|
"p-limit": "^3.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@gitbeaker/core": {
|
||||||
|
"version": "42.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@gitbeaker/core/-/core-42.5.0.tgz",
|
||||||
|
"integrity": "sha512-rMWpOPaZi1iLiifnOIoVO57p2EmQQdfIwP4txqNyMvG4WjYP5Ez0U7jRD9Nra41x6K5kTPBZkuQcAdxVWRJcEQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@gitbeaker/requester-utils": "^42.5.0",
|
||||||
|
"qs": "^6.12.2",
|
||||||
|
"xcase": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.20.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@gitbeaker/requester-utils": {
|
||||||
|
"version": "42.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@gitbeaker/requester-utils/-/requester-utils-42.5.0.tgz",
|
||||||
|
"integrity": "sha512-HLdLS9LPBMVQumvroQg/4qkphLDtwDB+ygEsrD2u4oYCMUtXV4V1xaVqU4yTXjbTJ5sItOtdB43vYRkBcgueBw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"picomatch-browser": "^2.2.6",
|
||||||
|
"qs": "^6.12.2",
|
||||||
|
"rate-limiter-flexible": "^4.0.1",
|
||||||
|
"xcase": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.20.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@gitbeaker/rest": {
|
||||||
|
"version": "42.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@gitbeaker/rest/-/rest-42.5.0.tgz",
|
||||||
|
"integrity": "sha512-oC5cM6jS7aFOp0luTw5mWSRuMgdxwHRLZQ/aWkI+ETMfsprR/HyxsXfljlMY/XJ/fRxTbRJiodR5Axf66WjO3w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@gitbeaker/core": "^42.5.0",
|
||||||
|
"@gitbeaker/requester-utils": "^42.5.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.20.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@google-cloud/kms": {
|
"node_modules/@google-cloud/kms": {
|
||||||
"version": "4.5.0",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@google-cloud/kms/-/kms-4.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@google-cloud/kms/-/kms-4.5.0.tgz",
|
||||||
@@ -24628,6 +24671,18 @@
|
|||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/picomatch-browser": {
|
||||||
|
"version": "2.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/picomatch-browser/-/picomatch-browser-2.2.6.tgz",
|
||||||
|
"integrity": "sha512-0ypsOQt9D4e3hziV8O4elD9uN0z/jtUEfxVRtNaAAtXIyUx9m/SzlO020i8YNL2aL/E6blOvvHQcin6HZlFy/w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pify": {
|
"node_modules/pify": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
|
||||||
@@ -25562,6 +25617,12 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rate-limiter-flexible": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-2/dGHpDFpeA0+755oUkW+EKyklqLS9lu0go9pDsbhqQjZcxfRyJ6LA4JI0+HAdZ2bemD/oOjUeZQB2lCZqXQfQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/raw-body": {
|
"node_modules/raw-body": {
|
||||||
"version": "2.5.2",
|
"version": "2.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
|
||||||
@@ -31039,6 +31100,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/xcase": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/xcase/-/xcase-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-UmFXIPU+9Eg3E9m/728Bii0lAIuoc+6nbrNUKaRPJOFp91ih44qqGlWtxMB6kXFrRD6po+86ksHM5XHCfk6iPw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/xml-crypto": {
|
"node_modules/xml-crypto": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-6.0.1.tgz",
|
||||||
|
|||||||
@@ -149,6 +149,7 @@
|
|||||||
"@fastify/static": "^7.0.4",
|
"@fastify/static": "^7.0.4",
|
||||||
"@fastify/swagger": "^8.14.0",
|
"@fastify/swagger": "^8.14.0",
|
||||||
"@fastify/swagger-ui": "^2.1.0",
|
"@fastify/swagger-ui": "^2.1.0",
|
||||||
|
"@gitbeaker/rest": "^42.5.0",
|
||||||
"@google-cloud/kms": "^4.5.0",
|
"@google-cloud/kms": "^4.5.0",
|
||||||
"@infisical/quic": "^1.0.8",
|
"@infisical/quic": "^1.0.8",
|
||||||
"@node-saml/passport-saml": "^5.0.1",
|
"@node-saml/passport-saml": "^5.0.1",
|
||||||
|
|||||||
@@ -2409,10 +2409,11 @@ export const SecretSyncs = {
|
|||||||
appId: "The ID of the Fly.io app to sync secrets to."
|
appId: "The ID of the Fly.io app to sync secrets to."
|
||||||
},
|
},
|
||||||
GITLAB: {
|
GITLAB: {
|
||||||
projectId: "The GitLab project to sync secrets to.",
|
projectId: "The GitLab Project ID to sync secrets to.",
|
||||||
projectName: "The GitLab project name to sync secrets to.",
|
projectName: "The GitLab Project Name to sync secrets to.",
|
||||||
groupId: "The GitLab group to sync secrets to.",
|
groupId: "The GitLab Group ID to sync secrets to.",
|
||||||
scope: "The GitLab project scope that secrets should be synced to. (default: individual)",
|
groupName: "The GitLab Group Name to sync secrets to.",
|
||||||
|
scope: "The GitLab scope that secrets should be synced to. (default: project)",
|
||||||
targetEnvironment: "The GitLab environment scope that secrets should be synced to. (default: *)",
|
targetEnvironment: "The GitLab environment scope that secrets should be synced to. (default: *)",
|
||||||
shouldProtectSecrets: "Whether variables should be protected",
|
shouldProtectSecrets: "Whether variables should be protected",
|
||||||
shouldMaskSecrets: "Whether variables should be masked in logs",
|
shouldMaskSecrets: "Whether variables should be masked in logs",
|
||||||
|
|||||||
@@ -247,6 +247,10 @@ const envSchema = z
|
|||||||
INF_APP_CONNECTION_GITHUB_RADAR_APP_ID: zpStr(z.string().optional()),
|
INF_APP_CONNECTION_GITHUB_RADAR_APP_ID: zpStr(z.string().optional()),
|
||||||
INF_APP_CONNECTION_GITHUB_RADAR_APP_WEBHOOK_SECRET: zpStr(z.string().optional()),
|
INF_APP_CONNECTION_GITHUB_RADAR_APP_WEBHOOK_SECRET: zpStr(z.string().optional()),
|
||||||
|
|
||||||
|
// gitlab oauth
|
||||||
|
INF_APP_CONNECTION_GITLAB_OAUTH_CLIENT_ID: zpStr(z.string().optional()),
|
||||||
|
INF_APP_CONNECTION_GITLAB_OAUTH_CLIENT_SECRET: zpStr(z.string().optional()),
|
||||||
|
|
||||||
// gcp app
|
// gcp app
|
||||||
INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL: zpStr(z.string().optional()),
|
INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL: zpStr(z.string().optional()),
|
||||||
|
|
||||||
|
|||||||
@@ -34,9 +34,6 @@ export const registerGitLabConnectionRouter = async (server: FastifyZodProvider)
|
|||||||
params: z.object({
|
params: z.object({
|
||||||
connectionId: z.string().uuid()
|
connectionId: z.string().uuid()
|
||||||
}),
|
}),
|
||||||
querystring: z.object({
|
|
||||||
group: z.string().optional()
|
|
||||||
}),
|
|
||||||
response: {
|
response: {
|
||||||
200: z
|
200: z
|
||||||
.object({
|
.object({
|
||||||
@@ -52,8 +49,7 @@ export const registerGitLabConnectionRouter = async (server: FastifyZodProvider)
|
|||||||
|
|
||||||
const projects: TGitLabProject[] = await server.services.appConnection.gitlab.listProjects(
|
const projects: TGitLabProject[] = await server.services.appConnection.gitlab.listProjects(
|
||||||
connectionId,
|
connectionId,
|
||||||
req.permission,
|
req.permission
|
||||||
req.query.group
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return projects;
|
return projects;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
import { AxiosError, AxiosResponse } from "axios";
|
import { GitbeakerRequestError, Gitlab } from "@gitbeaker/rest";
|
||||||
|
import { AxiosError } from "axios";
|
||||||
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { request } from "@app/lib/config/request";
|
import { request } from "@app/lib/config/request";
|
||||||
@@ -13,7 +14,7 @@ import { IntegrationUrls } from "@app/services/integration-auth/integration-list
|
|||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
|
|
||||||
import { TAppConnectionDALFactory } from "../app-connection-dal";
|
import { TAppConnectionDALFactory } from "../app-connection-dal";
|
||||||
import { GitLabConnectionMethod } from "./gitlab-connection-enums";
|
import { GitLabAccessTokenType, GitLabConnectionMethod } from "./gitlab-connection-enums";
|
||||||
import { TGitLabConnection, TGitLabConnectionConfig, TGitLabGroup, TGitLabProject } from "./gitlab-connection-types";
|
import { TGitLabConnection, TGitLabConnectionConfig, TGitLabGroup, TGitLabProject } from "./gitlab-connection-types";
|
||||||
|
|
||||||
interface GitLabOAuthTokenResponse {
|
interface GitLabOAuthTokenResponse {
|
||||||
@@ -26,7 +27,7 @@ interface GitLabOAuthTokenResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getGitLabConnectionListItem = () => {
|
export const getGitLabConnectionListItem = () => {
|
||||||
const { CLIENT_ID_GITLAB_LOGIN } = getConfig();
|
const { INF_APP_CONNECTION_GITLAB_OAUTH_CLIENT_ID } = getConfig();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: "GitLab" as const,
|
name: "GitLab" as const,
|
||||||
@@ -35,7 +36,7 @@ export const getGitLabConnectionListItem = () => {
|
|||||||
GitLabConnectionMethod.AccessToken,
|
GitLabConnectionMethod.AccessToken,
|
||||||
GitLabConnectionMethod.OAuth
|
GitLabConnectionMethod.OAuth
|
||||||
],
|
],
|
||||||
oauthClientId: CLIENT_ID_GITLAB_LOGIN
|
oauthClientId: INF_APP_CONNECTION_GITLAB_OAUTH_CLIENT_ID
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -47,6 +48,18 @@ export const getGitLabInstanceUrl = async (instanceUrl?: string) => {
|
|||||||
return gitLabInstanceUrl;
|
return gitLabInstanceUrl;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getGitLabClient = async (accessToken: string, instanceUrl?: string, isOAuth = false) => {
|
||||||
|
const host = await getGitLabInstanceUrl(instanceUrl);
|
||||||
|
|
||||||
|
const client = new Gitlab<true>({
|
||||||
|
host,
|
||||||
|
...(isOAuth ? { oauthToken: accessToken } : { token: accessToken }),
|
||||||
|
camelize: true
|
||||||
|
});
|
||||||
|
|
||||||
|
return client;
|
||||||
|
};
|
||||||
|
|
||||||
export const refreshGitLabToken = async (
|
export const refreshGitLabToken = async (
|
||||||
refreshToken: string,
|
refreshToken: string,
|
||||||
appId: string,
|
appId: string,
|
||||||
@@ -55,8 +68,9 @@ export const refreshGitLabToken = async (
|
|||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">,
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">,
|
||||||
instanceUrl?: string
|
instanceUrl?: string
|
||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
const { CLIENT_ID_GITLAB_LOGIN, CLIENT_SECRET_GITLAB_LOGIN, SITE_URL } = getConfig();
|
const { INF_APP_CONNECTION_GITLAB_OAUTH_CLIENT_ID, INF_APP_CONNECTION_GITLAB_OAUTH_CLIENT_SECRET, SITE_URL } =
|
||||||
if (!CLIENT_SECRET_GITLAB_LOGIN || !CLIENT_ID_GITLAB_LOGIN || !SITE_URL) {
|
getConfig();
|
||||||
|
if (!INF_APP_CONNECTION_GITLAB_OAUTH_CLIENT_SECRET || !INF_APP_CONNECTION_GITLAB_OAUTH_CLIENT_ID || !SITE_URL) {
|
||||||
throw new InternalServerError({
|
throw new InternalServerError({
|
||||||
message: `GitLab environment variables have not been configured`
|
message: `GitLab environment variables have not been configured`
|
||||||
});
|
});
|
||||||
@@ -65,9 +79,9 @@ export const refreshGitLabToken = async (
|
|||||||
const payload = new URLSearchParams({
|
const payload = new URLSearchParams({
|
||||||
grant_type: "refresh_token",
|
grant_type: "refresh_token",
|
||||||
refresh_token: refreshToken,
|
refresh_token: refreshToken,
|
||||||
client_id: CLIENT_ID_GITLAB_LOGIN,
|
client_id: INF_APP_CONNECTION_GITLAB_OAUTH_CLIENT_ID,
|
||||||
client_secret: CLIENT_SECRET_GITLAB_LOGIN,
|
client_secret: INF_APP_CONNECTION_GITLAB_OAUTH_CLIENT_SECRET,
|
||||||
redirect_uri: `${SITE_URL}/integrations/gitlab/oauth2/callback`
|
redirect_uri: `${SITE_URL}/organization/app-connections/gitlab/oauth/callback`
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -95,7 +109,6 @@ export const refreshGitLabToken = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
await appConnectionDAL.updateById(appId, { encryptedCredentials });
|
await appConnectionDAL.updateById(appId, { encryptedCredentials });
|
||||||
|
|
||||||
return data.access_token;
|
return data.access_token;
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof AxiosError) {
|
if (error instanceof AxiosError) {
|
||||||
@@ -113,8 +126,9 @@ export const exchangeGitLabOAuthCode = async (
|
|||||||
code: string,
|
code: string,
|
||||||
instanceUrl?: string
|
instanceUrl?: string
|
||||||
): Promise<GitLabOAuthTokenResponse> => {
|
): Promise<GitLabOAuthTokenResponse> => {
|
||||||
const { CLIENT_ID_GITLAB_LOGIN, CLIENT_SECRET_GITLAB_LOGIN, SITE_URL } = getConfig();
|
const { INF_APP_CONNECTION_GITLAB_OAUTH_CLIENT_ID, INF_APP_CONNECTION_GITLAB_OAUTH_CLIENT_SECRET, SITE_URL } =
|
||||||
if (!CLIENT_SECRET_GITLAB_LOGIN || !CLIENT_ID_GITLAB_LOGIN || !SITE_URL) {
|
getConfig();
|
||||||
|
if (!INF_APP_CONNECTION_GITLAB_OAUTH_CLIENT_SECRET || !INF_APP_CONNECTION_GITLAB_OAUTH_CLIENT_ID || !SITE_URL) {
|
||||||
throw new InternalServerError({
|
throw new InternalServerError({
|
||||||
message: `GitLab environment variables have not been configured`
|
message: `GitLab environment variables have not been configured`
|
||||||
});
|
});
|
||||||
@@ -124,9 +138,9 @@ export const exchangeGitLabOAuthCode = async (
|
|||||||
const payload = new URLSearchParams({
|
const payload = new URLSearchParams({
|
||||||
grant_type: "authorization_code",
|
grant_type: "authorization_code",
|
||||||
code,
|
code,
|
||||||
client_id: CLIENT_ID_GITLAB_LOGIN,
|
client_id: INF_APP_CONNECTION_GITLAB_OAUTH_CLIENT_ID,
|
||||||
client_secret: CLIENT_SECRET_GITLAB_LOGIN,
|
client_secret: INF_APP_CONNECTION_GITLAB_OAUTH_CLIENT_SECRET,
|
||||||
redirect_uri: `${SITE_URL}/integrations/gitlab/oauth2/callback`
|
redirect_uri: `${SITE_URL}/organization/app-connections/gitlab/oauth/callback`
|
||||||
});
|
});
|
||||||
const url = await getGitLabInstanceUrl(instanceUrl);
|
const url = await getGitLabInstanceUrl(instanceUrl);
|
||||||
|
|
||||||
@@ -173,30 +187,24 @@ export const validateGitLabConnectionCredentials = async (config: TGitLabConnect
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let response: AxiosResponse<TGitLabProject[]> | null = null;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const url = await getGitLabInstanceUrl(inputCredentials.instanceUrl);
|
const client = await getGitLabClient(
|
||||||
response = await request.get<TGitLabProject[]>(`${url}/api/v4/user`, {
|
accessToken,
|
||||||
headers: {
|
inputCredentials.instanceUrl,
|
||||||
Authorization: `Bearer ${accessToken}`,
|
method === GitLabConnectionMethod.OAuth
|
||||||
Accept: "application/json"
|
);
|
||||||
}
|
await client.Users.showCurrentUser();
|
||||||
});
|
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof AxiosError) {
|
logger.error(error, "Error validating GitLab connection credentials");
|
||||||
|
|
||||||
|
if (error instanceof GitbeakerRequestError) {
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: `Failed to validate credentials: ${error.message}`
|
message: `Failed to validate credentials: ${error.message ?? "Unknown error"}${error.cause?.description && error.message !== "Unauthorized" ? `. Cause: ${error.cause.description}` : ""}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Unable to validate connection: verify credentials"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response?.data) {
|
throw new BadRequestError({
|
||||||
throw new InternalServerError({
|
message: `Failed to validate credentials: ${(error as Error)?.message || "verify credentials"}`
|
||||||
message: "Failed to validate credentials: Response was empty"
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,20 +225,18 @@ export const validateGitLabConnectionCredentials = async (config: TGitLabConnect
|
|||||||
export const listGitLabProjects = async ({
|
export const listGitLabProjects = async ({
|
||||||
appConnection,
|
appConnection,
|
||||||
appConnectionDAL,
|
appConnectionDAL,
|
||||||
kmsService,
|
kmsService
|
||||||
teamId
|
|
||||||
}: {
|
}: {
|
||||||
appConnection: TGitLabConnection;
|
appConnection: TGitLabConnection;
|
||||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">;
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
teamId?: string;
|
|
||||||
}): Promise<TGitLabProject[]> => {
|
}): Promise<TGitLabProject[]> => {
|
||||||
let { accessToken } = appConnection.credentials;
|
let { accessToken } = appConnection.credentials;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
appConnection.method === GitLabConnectionMethod.OAuth &&
|
appConnection.method === GitLabConnectionMethod.OAuth &&
|
||||||
appConnection.credentials.refreshToken &&
|
appConnection.credentials.refreshToken &&
|
||||||
appConnection.credentials.expiresAt < new Date()
|
new Date(appConnection.credentials.expiresAt) < new Date()
|
||||||
) {
|
) {
|
||||||
accessToken = await refreshGitLabToken(
|
accessToken = await refreshGitLabToken(
|
||||||
appConnection.credentials.refreshToken,
|
appConnection.credentials.refreshToken,
|
||||||
@@ -242,160 +248,29 @@ export const listGitLabProjects = async ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = await getGitLabInstanceUrl(appConnection.credentials.instanceUrl);
|
|
||||||
const gitLabApiUrl = `${url}/api/v4`;
|
|
||||||
|
|
||||||
const projects: TGitLabProject[] = [];
|
|
||||||
let page = 1;
|
|
||||||
const perPage = 100;
|
|
||||||
let hasMorePages = true;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (teamId) {
|
const client = await getGitLabClient(
|
||||||
while (hasMorePages) {
|
accessToken,
|
||||||
const { data } = await request.get<TGitLabProject[]>(`${gitLabApiUrl}/groups/${teamId}/projects`, {
|
appConnection.credentials.instanceUrl,
|
||||||
headers: {
|
appConnection.method === GitLabConnectionMethod.OAuth
|
||||||
Authorization: `Bearer ${accessToken}`,
|
);
|
||||||
Accept: "application/json"
|
const projects = await client.Projects.all({
|
||||||
},
|
archived: false,
|
||||||
params: {
|
includePendingDelete: false,
|
||||||
page: page.toString(),
|
membership: true,
|
||||||
per_page: perPage.toString(),
|
includeHidden: false,
|
||||||
order_by: "updated_at",
|
imported: false
|
||||||
sort: "desc",
|
});
|
||||||
include_subgroups: "true"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!data) {
|
return projects.map((project) => ({
|
||||||
throw new InternalServerError({
|
name: project.pathWithNamespace,
|
||||||
message: "Failed to get group projects: Response was empty"
|
id: project.id.toString()
|
||||||
});
|
}));
|
||||||
}
|
|
||||||
|
|
||||||
data.forEach((project) => {
|
|
||||||
projects.push({
|
|
||||||
name: project.name,
|
|
||||||
id: project.id.toString()
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
hasMorePages = data.length === perPage;
|
|
||||||
page += 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const { data: userData } = await request.get<{ id: string }>(`${gitLabApiUrl}/user`, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${accessToken}`,
|
|
||||||
Accept: "application/json"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!userData?.id) {
|
|
||||||
throw new InternalServerError({
|
|
||||||
message: "Failed to get current user information"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
while (hasMorePages) {
|
|
||||||
const { data } = await request.get<TGitLabProject[]>(`${gitLabApiUrl}/users/${userData.id}/projects`, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${accessToken}`,
|
|
||||||
Accept: "application/json"
|
|
||||||
},
|
|
||||||
params: {
|
|
||||||
page: page.toString(),
|
|
||||||
per_page: perPage.toString(),
|
|
||||||
order_by: "updated_at",
|
|
||||||
sort: "desc"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
throw new InternalServerError({
|
|
||||||
message: "Failed to get user projects: Response was empty"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
data.forEach((project) => {
|
|
||||||
projects.push({
|
|
||||||
name: project.name,
|
|
||||||
id: project.id.toString()
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
hasMorePages = data.length === perPage;
|
|
||||||
page += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (projects.length === 0 && appConnection.method === GitLabConnectionMethod.AccessToken) {
|
|
||||||
try {
|
|
||||||
const { data: tokenAssociations } = await request.get<{
|
|
||||||
projects?: TGitLabProject[];
|
|
||||||
groups?: Array<{ projects?: TGitLabProject[] }>;
|
|
||||||
}>(`${gitLabApiUrl}/personal_access_tokens/self/associations`, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${accessToken}`,
|
|
||||||
Accept: "application/json"
|
|
||||||
},
|
|
||||||
params: {
|
|
||||||
min_access_level: "50"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (tokenAssociations?.projects) {
|
|
||||||
tokenAssociations.projects.forEach((project) => {
|
|
||||||
projects.push({
|
|
||||||
name: project.name,
|
|
||||||
id: project.id.toString()
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tokenAssociations?.groups) {
|
|
||||||
tokenAssociations.groups.forEach((group) => {
|
|
||||||
if (group.projects) {
|
|
||||||
group.projects.forEach((project) => {
|
|
||||||
const existingProject = projects.find((p) => p.id === project.id.toString());
|
|
||||||
if (!existingProject) {
|
|
||||||
projects.push({
|
|
||||||
name: project.name,
|
|
||||||
id: project.id.toString()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.warn(error, "Failed to fetch projects via personal access token associations:");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return projects;
|
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof AxiosError) {
|
if (error instanceof GitbeakerRequestError) {
|
||||||
const status = error.response?.status;
|
throw new BadRequestError({
|
||||||
const { message } = error;
|
message: `Failed to fetch GitLab projects: ${error.message ?? "Unknown error"}${error.cause?.description && error.message !== "Unauthorized" ? `. Cause: ${error.cause.description}` : ""}`
|
||||||
|
});
|
||||||
if (status === 401) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `GitLab authentication failed: ${message}`
|
|
||||||
});
|
|
||||||
} else if (status === 403) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `GitLab access forbidden: ${message}`
|
|
||||||
});
|
|
||||||
} else if (status === 404) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: teamId ? `GitLab group not found or access denied: ${message}` : `GitLab user not found: ${message}`
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `Failed to fetch GitLab projects: ${message}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error instanceof InternalServerError) {
|
if (error instanceof InternalServerError) {
|
||||||
@@ -411,22 +286,25 @@ export const listGitLabProjects = async ({
|
|||||||
export const listGitLabGroups = async ({
|
export const listGitLabGroups = async ({
|
||||||
appConnection,
|
appConnection,
|
||||||
appConnectionDAL,
|
appConnectionDAL,
|
||||||
kmsService,
|
kmsService
|
||||||
includeSubgroups = true,
|
|
||||||
owned = false
|
|
||||||
}: {
|
}: {
|
||||||
appConnection: TGitLabConnection;
|
appConnection: TGitLabConnection;
|
||||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">;
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
includeSubgroups?: boolean;
|
|
||||||
owned?: boolean;
|
|
||||||
}): Promise<TGitLabGroup[]> => {
|
}): Promise<TGitLabGroup[]> => {
|
||||||
let { accessToken } = appConnection.credentials;
|
let { accessToken } = appConnection.credentials;
|
||||||
|
|
||||||
|
if (
|
||||||
|
appConnection.method === GitLabConnectionMethod.AccessToken &&
|
||||||
|
appConnection.credentials.accessTokenType === GitLabAccessTokenType.Project
|
||||||
|
) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
appConnection.method === GitLabConnectionMethod.OAuth &&
|
appConnection.method === GitLabConnectionMethod.OAuth &&
|
||||||
appConnection.credentials.refreshToken &&
|
appConnection.credentials.refreshToken &&
|
||||||
appConnection.credentials.expiresAt < new Date()
|
new Date(appConnection.credentials.expiresAt) < new Date()
|
||||||
) {
|
) {
|
||||||
accessToken = await refreshGitLabToken(
|
accessToken = await refreshGitLabToken(
|
||||||
appConnection.credentials.refreshToken,
|
appConnection.credentials.refreshToken,
|
||||||
@@ -438,69 +316,28 @@ export const listGitLabGroups = async ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = await getGitLabInstanceUrl(appConnection.credentials.instanceUrl);
|
|
||||||
const gitLabApiUrl = `${url}/api/v4`;
|
|
||||||
|
|
||||||
const groups: TGitLabGroup[] = [];
|
|
||||||
let page = 1;
|
|
||||||
const perPage = 100;
|
|
||||||
let hasMorePages = true;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
while (hasMorePages) {
|
const client = await getGitLabClient(
|
||||||
const { data } = await request.get<TGitLabGroup[]>(`${gitLabApiUrl}/groups`, {
|
accessToken,
|
||||||
headers: {
|
appConnection.credentials.instanceUrl,
|
||||||
Authorization: `Bearer ${accessToken}`,
|
appConnection.method === GitLabConnectionMethod.OAuth
|
||||||
Accept: "application/json"
|
);
|
||||||
},
|
|
||||||
params: {
|
|
||||||
page: page.toString(),
|
|
||||||
per_page: perPage.toString(),
|
|
||||||
order_by: "name",
|
|
||||||
sort: "asc",
|
|
||||||
all_available: (!owned).toString(),
|
|
||||||
owned: owned.toString(),
|
|
||||||
min_access_level: "10",
|
|
||||||
...(includeSubgroups && { with_custom_attributes: "true" })
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!data) {
|
const groups = await client.Groups.all({
|
||||||
throw new InternalServerError({
|
orderBy: "name",
|
||||||
message: "Failed to get groups: Response was empty"
|
sort: "asc",
|
||||||
});
|
minAccessLevel: 50
|
||||||
}
|
});
|
||||||
|
|
||||||
data.forEach((group) => {
|
return groups.map((group) => ({
|
||||||
groups.push({
|
id: group.id.toString(),
|
||||||
id: group.id.toString(),
|
name: group.name
|
||||||
name: group.name
|
}));
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
hasMorePages = data.length === perPage;
|
|
||||||
page += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return groups;
|
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof AxiosError) {
|
if (error instanceof GitbeakerRequestError) {
|
||||||
const status = error.response?.status;
|
throw new BadRequestError({
|
||||||
const { message } = error;
|
message: `Failed to fetch GitLab groups: ${error.message ?? "Unknown error"}${error.cause?.description && error.message !== "Unauthorized" ? `. Cause: ${error.cause.description}` : ""}`
|
||||||
|
});
|
||||||
if (status === 401) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `GitLab authentication failed: ${message}`
|
|
||||||
});
|
|
||||||
} else if (status === 403) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `GitLab access forbidden: ${message}`
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `Failed to fetch GitLab groups: ${message}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error instanceof InternalServerError) {
|
if (error instanceof InternalServerError) {
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ export const gitlabConnectionService = (
|
|||||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "updateById">,
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
|
||||||
) => {
|
) => {
|
||||||
const listProjects = async (connectionId: string, actor: OrgServiceActor, teamId?: string) => {
|
const listProjects = async (connectionId: string, actor: OrgServiceActor) => {
|
||||||
try {
|
try {
|
||||||
const appConnection = await getAppConnection(AppConnection.GitLab, connectionId, actor);
|
const appConnection = await getAppConnection(AppConnection.GitLab, connectionId, actor);
|
||||||
const projects = await listGitLabProjects({ appConnection, appConnectionDAL, kmsService, teamId });
|
const projects = await listGitLabProjects({ appConnection, appConnectionDAL, kmsService });
|
||||||
return projects;
|
return projects;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error, `Failed to establish connection with GitLab for app ${connectionId}`);
|
logger.error(error, `Failed to establish connection with GitLab for app ${connectionId}`);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export enum GitLabSyncScope {
|
export enum GitLabSyncScope {
|
||||||
Individual = "individual",
|
Project = "project",
|
||||||
Group = "group"
|
Group = "group"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
import { request } from "@app/lib/config/request";
|
import { GitbeakerRequestError } from "@gitbeaker/rest";
|
||||||
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
|
||||||
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
||||||
import { GitLabConnectionMethod, refreshGitLabToken, TGitLabConnection } from "@app/services/app-connection/gitlab";
|
import {
|
||||||
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
getGitLabClient,
|
||||||
|
GitLabConnectionMethod,
|
||||||
|
refreshGitLabToken,
|
||||||
|
TGitLabConnection
|
||||||
|
} from "@app/services/app-connection/gitlab";
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
import { TGitLabSyncWithCredentials, TGitLabVariable } from "@app/services/secret-sync/gitlab/gitlab-sync-types";
|
import { TGitLabSyncWithCredentials, TGitLabVariable } from "@app/services/secret-sync/gitlab/gitlab-sync-types";
|
||||||
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
|
import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
|
||||||
@@ -11,6 +15,7 @@ import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
|
|||||||
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
|
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
|
||||||
|
|
||||||
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
import { SECRET_SYNC_NAME_MAP } from "../secret-sync-maps";
|
||||||
|
import { GitLabSyncScope } from "./gitlab-sync-enums";
|
||||||
|
|
||||||
interface TGitLabVariablePayload {
|
interface TGitLabVariablePayload {
|
||||||
key?: string;
|
key?: string;
|
||||||
@@ -42,81 +47,64 @@ const getValidAccessToken = async (
|
|||||||
if (
|
if (
|
||||||
connection.method === GitLabConnectionMethod.OAuth &&
|
connection.method === GitLabConnectionMethod.OAuth &&
|
||||||
connection.credentials.refreshToken &&
|
connection.credentials.refreshToken &&
|
||||||
connection.credentials.expiresAt < new Date()
|
new Date(connection.credentials.expiresAt) < new Date()
|
||||||
) {
|
) {
|
||||||
const accessToken = await refreshGitLabToken(
|
const accessToken = await refreshGitLabToken(
|
||||||
connection.credentials.refreshToken,
|
connection.credentials.refreshToken,
|
||||||
connection.id,
|
connection.id,
|
||||||
connection.orgId,
|
connection.orgId,
|
||||||
appConnectionDAL,
|
appConnectionDAL,
|
||||||
kmsService
|
kmsService,
|
||||||
|
connection.credentials.instanceUrl
|
||||||
);
|
);
|
||||||
return accessToken;
|
return accessToken;
|
||||||
}
|
}
|
||||||
return connection.credentials.accessToken;
|
return connection.credentials.accessToken;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getGitLabApiUrl = async (connection: TGitLabConnection): Promise<string> => {
|
|
||||||
const baseUrl = connection.credentials.instanceUrl || IntegrationUrls.GITLAB_API_URL;
|
|
||||||
await blockLocalAndPrivateIpAddresses(baseUrl);
|
|
||||||
return baseUrl.includes("/api") ? baseUrl : `${baseUrl}/api`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const buildVariablesEndpoint = (apiUrl: string, projectId: string): string => {
|
|
||||||
return `${apiUrl}/v4/projects/${encodeURIComponent(projectId)}/variables`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getGitLabVariables = async ({
|
const getGitLabVariables = async ({
|
||||||
accessToken,
|
accessToken,
|
||||||
connection,
|
connection,
|
||||||
projectId,
|
scope,
|
||||||
|
resourceId,
|
||||||
targetEnvironment
|
targetEnvironment
|
||||||
}: {
|
}: {
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
connection: TGitLabConnection;
|
connection: TGitLabConnection;
|
||||||
projectId: string;
|
scope: GitLabSyncScope;
|
||||||
|
resourceId: string;
|
||||||
targetEnvironment?: string;
|
targetEnvironment?: string;
|
||||||
}): Promise<TGitLabVariable[]> => {
|
}): Promise<TGitLabVariable[]> => {
|
||||||
try {
|
try {
|
||||||
const apiUrl = await getGitLabApiUrl(connection);
|
const client = await getGitLabClient(
|
||||||
const baseEndpoint = buildVariablesEndpoint(apiUrl, projectId);
|
accessToken,
|
||||||
|
connection.credentials.instanceUrl,
|
||||||
|
connection.method === GitLabConnectionMethod.OAuth
|
||||||
|
);
|
||||||
|
|
||||||
const headers = {
|
let variables: TGitLabVariable[] = [];
|
||||||
Authorization: `Bearer ${accessToken}`,
|
|
||||||
"Accept-Encoding": "application/json",
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
};
|
|
||||||
|
|
||||||
let allVariables: TGitLabVariable[] = [];
|
if (scope === GitLabSyncScope.Project) {
|
||||||
let url: string | null = `${baseEndpoint}?per_page=100`;
|
variables = await client.ProjectVariables.all(resourceId);
|
||||||
|
} else {
|
||||||
if (targetEnvironment) {
|
variables = await client.GroupVariables.all(resourceId);
|
||||||
url += `&filter[environment_scope]=${encodeURIComponent(targetEnvironment)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (url) {
|
|
||||||
const response = await request.get<TGitLabVariable[]>(url, { headers });
|
|
||||||
allVariables = [...allVariables, ...(response.data || [])];
|
|
||||||
|
|
||||||
const linkHeader = response.headers.link as string;
|
|
||||||
const nextLink = linkHeader?.split(",").find((part: string) => part.includes('rel="next"'));
|
|
||||||
|
|
||||||
if (nextLink) {
|
|
||||||
url = nextLink.trim().split(";")[0].slice(1, -1);
|
|
||||||
} else {
|
|
||||||
url = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetEnvironment) {
|
if (targetEnvironment) {
|
||||||
return allVariables.filter((variable) => variable.environment_scope === targetEnvironment);
|
variables = variables.filter((v) => v.environmentScope === targetEnvironment);
|
||||||
}
|
}
|
||||||
|
|
||||||
return allVariables;
|
return variables;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error instanceof GitbeakerRequestError) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error: new Error(
|
||||||
|
`Failed to fetch variables: ${error.message ?? "Unknown error"}${error.cause?.description && error.message !== "Unauthorized" ? `. Cause: ${error.cause.description}` : ""}`
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
throw new SecretSyncError({
|
throw new SecretSyncError({
|
||||||
error,
|
error
|
||||||
secretKey: "list_variables"
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -124,38 +112,61 @@ const getGitLabVariables = async ({
|
|||||||
const createGitLabVariable = async ({
|
const createGitLabVariable = async ({
|
||||||
accessToken,
|
accessToken,
|
||||||
connection,
|
connection,
|
||||||
projectId,
|
scope,
|
||||||
|
resourceId,
|
||||||
variable
|
variable
|
||||||
}: {
|
}: {
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
connection: TGitLabConnection;
|
connection: TGitLabConnection;
|
||||||
projectId: string;
|
scope: GitLabSyncScope;
|
||||||
|
resourceId: string;
|
||||||
variable: TGitLabVariableCreate;
|
variable: TGitLabVariableCreate;
|
||||||
}): Promise<void> => {
|
}): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const apiUrl = await getGitLabApiUrl(connection);
|
const client = await getGitLabClient(
|
||||||
const endpoint = buildVariablesEndpoint(apiUrl, projectId);
|
accessToken,
|
||||||
|
connection.credentials.instanceUrl,
|
||||||
|
connection.method === GitLabConnectionMethod.OAuth
|
||||||
|
);
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
key: variable.key,
|
key: variable.key,
|
||||||
value: variable.value,
|
value: variable.value,
|
||||||
variable_type: variable.variable_type || "env_var",
|
variableType: "env_var",
|
||||||
environment_scope: variable.environment_scope || "*",
|
environmentScope: variable.environment_scope || "*",
|
||||||
protected: variable.protected || false,
|
protected: variable.protected || false,
|
||||||
masked: variable.masked || false,
|
masked: variable.masked || false,
|
||||||
masked_and_hidden: variable.masked_and_hidden || false,
|
masked_and_hidden: variable.masked_and_hidden || false,
|
||||||
raw: false,
|
raw: false
|
||||||
...(variable.description && { description: variable.description })
|
|
||||||
};
|
};
|
||||||
|
|
||||||
await request.post(endpoint, payload, {
|
if (scope === GitLabSyncScope.Project) {
|
||||||
headers: {
|
await client.ProjectVariables.create(resourceId, payload.key, payload.value, {
|
||||||
Authorization: `Bearer ${accessToken}`,
|
variableType: "env_var",
|
||||||
"Accept-Encoding": "application/json",
|
environmentScope: payload.environmentScope,
|
||||||
"Content-Type": "application/json"
|
protected: payload.protected,
|
||||||
}
|
masked: payload.masked,
|
||||||
});
|
masked_and_hidden: payload.masked_and_hidden,
|
||||||
|
raw: false
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await client.GroupVariables.create(resourceId, payload.key, payload.value, {
|
||||||
|
variableType: "env_var",
|
||||||
|
environmentScope: payload.environmentScope,
|
||||||
|
protected: payload.protected,
|
||||||
|
masked: payload.masked,
|
||||||
|
raw: false
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error instanceof GitbeakerRequestError) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error: new Error(
|
||||||
|
`Failed to create variable: ${error.message ?? "Unknown error"}${error.cause?.description && error.message !== "Unauthorized" ? `. Cause: ${error.cause.description}` : ""}`
|
||||||
|
),
|
||||||
|
secretKey: variable.key
|
||||||
|
});
|
||||||
|
}
|
||||||
throw new SecretSyncError({
|
throw new SecretSyncError({
|
||||||
error,
|
error,
|
||||||
secretKey: variable.key
|
secretKey: variable.key
|
||||||
@@ -166,45 +177,57 @@ const createGitLabVariable = async ({
|
|||||||
const updateGitLabVariable = async ({
|
const updateGitLabVariable = async ({
|
||||||
accessToken,
|
accessToken,
|
||||||
connection,
|
connection,
|
||||||
projectId,
|
scope,
|
||||||
|
resourceId,
|
||||||
key,
|
key,
|
||||||
variable,
|
variable,
|
||||||
targetEnvironment
|
targetEnvironment
|
||||||
}: {
|
}: {
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
connection: TGitLabConnection;
|
connection: TGitLabConnection;
|
||||||
projectId: string;
|
scope: GitLabSyncScope;
|
||||||
|
resourceId: string;
|
||||||
key: string;
|
key: string;
|
||||||
variable: TGitLabVariableUpdate;
|
variable: TGitLabVariableUpdate;
|
||||||
targetEnvironment?: string;
|
targetEnvironment?: string;
|
||||||
}): Promise<void> => {
|
}): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const apiUrl = await getGitLabApiUrl(connection);
|
const client = await getGitLabClient(
|
||||||
const baseEndpoint = buildVariablesEndpoint(apiUrl, projectId);
|
accessToken,
|
||||||
let url = `${baseEndpoint}/${encodeURIComponent(key)}`;
|
connection.credentials.instanceUrl,
|
||||||
|
connection.method === GitLabConnectionMethod.OAuth
|
||||||
|
);
|
||||||
|
|
||||||
if (targetEnvironment) {
|
const options = {
|
||||||
url += `?filter[environment_scope]=${encodeURIComponent(targetEnvironment)}`;
|
...(variable.environment_scope && { environmentScope: variable.environment_scope }),
|
||||||
}
|
|
||||||
|
|
||||||
const payload = {
|
|
||||||
value: variable.value,
|
|
||||||
...(variable.variable_type && { variable_type: variable.variable_type }),
|
|
||||||
...(variable.environment_scope && { environment_scope: variable.environment_scope }),
|
|
||||||
...(variable.protected !== undefined && { protected: variable.protected }),
|
...(variable.protected !== undefined && { protected: variable.protected }),
|
||||||
...(variable.masked !== undefined && { masked: variable.masked }),
|
...(variable.masked !== undefined && { masked: variable.masked })
|
||||||
...(variable.masked_and_hidden !== undefined && { masked_and_hidden: variable.masked_and_hidden }),
|
|
||||||
...(variable.description !== undefined && { description: variable.description || "" })
|
|
||||||
};
|
};
|
||||||
|
|
||||||
await request.put(url, payload, {
|
if (targetEnvironment) {
|
||||||
headers: {
|
options.environmentScope = targetEnvironment;
|
||||||
Authorization: `Bearer ${accessToken}`,
|
}
|
||||||
"Accept-Encoding": "application/json",
|
|
||||||
"Content-Type": "application/json"
|
if (scope === GitLabSyncScope.Project) {
|
||||||
}
|
await client.ProjectVariables.edit(resourceId, key, variable.value, {
|
||||||
});
|
...options,
|
||||||
|
filter: { environment_scope: targetEnvironment || "*" }
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await client.GroupVariables.edit(resourceId, key, variable.value, {
|
||||||
|
...options,
|
||||||
|
filter: { environment_scope: targetEnvironment || "*" }
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error instanceof GitbeakerRequestError) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error: new Error(
|
||||||
|
`Failed to update variable: ${error.message ?? "Unknown error"}${error.cause?.description && error.message !== "Unauthorized" ? `. Cause: ${error.cause.description}` : ""}`
|
||||||
|
),
|
||||||
|
secretKey: key
|
||||||
|
});
|
||||||
|
}
|
||||||
throw new SecretSyncError({
|
throw new SecretSyncError({
|
||||||
error,
|
error,
|
||||||
secretKey: key
|
secretKey: key
|
||||||
@@ -215,35 +238,52 @@ const updateGitLabVariable = async ({
|
|||||||
const deleteGitLabVariable = async ({
|
const deleteGitLabVariable = async ({
|
||||||
accessToken,
|
accessToken,
|
||||||
connection,
|
connection,
|
||||||
projectId,
|
scope,
|
||||||
|
resourceId,
|
||||||
key,
|
key,
|
||||||
targetEnvironment
|
targetEnvironment,
|
||||||
|
allVariables
|
||||||
}: {
|
}: {
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
connection: TGitLabConnection;
|
connection: TGitLabConnection;
|
||||||
projectId: string;
|
scope: GitLabSyncScope;
|
||||||
|
resourceId: string;
|
||||||
key: string;
|
key: string;
|
||||||
targetEnvironment?: string;
|
targetEnvironment?: string;
|
||||||
|
allVariables?: TGitLabVariable[];
|
||||||
}): Promise<void> => {
|
}): Promise<void> => {
|
||||||
|
if (allVariables && !allVariables.find((v) => v.key === key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const apiUrl = await getGitLabApiUrl(connection);
|
const client = await getGitLabClient(
|
||||||
const baseEndpoint = buildVariablesEndpoint(apiUrl, projectId);
|
accessToken,
|
||||||
let url = `${baseEndpoint}/${encodeURIComponent(key)}`;
|
connection.credentials.instanceUrl,
|
||||||
|
connection.method === GitLabConnectionMethod.OAuth
|
||||||
|
);
|
||||||
|
|
||||||
|
const options: { filter?: { environment_scope: string } } = {};
|
||||||
if (targetEnvironment) {
|
if (targetEnvironment) {
|
||||||
url += `?filter[environment_scope]=${encodeURIComponent(targetEnvironment)}`;
|
options.filter = { environment_scope: targetEnvironment || "*" };
|
||||||
}
|
}
|
||||||
|
|
||||||
await request.delete(url, {
|
if (scope === GitLabSyncScope.Project) {
|
||||||
headers: {
|
await client.ProjectVariables.remove(resourceId, key, options);
|
||||||
Authorization: `Bearer ${accessToken}`,
|
} else {
|
||||||
"Accept-Encoding": "application/json",
|
await client.GroupVariables.remove(resourceId, key);
|
||||||
"Content-Type": "application/json"
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof GitbeakerRequestError) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
error: new Error(
|
||||||
|
`Failed to delete variable: ${error.message ?? "Unknown error"}${error.cause?.description && error.message !== "Unauthorized" ? `. Cause: ${error.cause.description}` : ""}`
|
||||||
|
),
|
||||||
|
secretKey: key
|
||||||
|
});
|
||||||
|
}
|
||||||
throw new SecretSyncError({
|
throw new SecretSyncError({
|
||||||
error
|
error,
|
||||||
|
secretKey: key
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -255,8 +295,9 @@ export const GitLabSyncFns = {
|
|||||||
{ appConnectionDAL, kmsService }: TGitLabSyncFactoryDeps
|
{ appConnectionDAL, kmsService }: TGitLabSyncFactoryDeps
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const { connection, environment, destinationConfig } = secretSync;
|
const { connection, environment, destinationConfig } = secretSync;
|
||||||
|
const { scope, targetEnvironment } = destinationConfig;
|
||||||
|
|
||||||
const { projectId, targetEnvironment } = destinationConfig;
|
const resourceId = scope === GitLabSyncScope.Project ? destinationConfig.projectId : destinationConfig.groupId;
|
||||||
|
|
||||||
const accessToken = await getValidAccessToken(connection, appConnectionDAL, kmsService);
|
const accessToken = await getValidAccessToken(connection, appConnectionDAL, kmsService);
|
||||||
|
|
||||||
@@ -264,13 +305,20 @@ export const GitLabSyncFns = {
|
|||||||
const currentVariables = await getGitLabVariables({
|
const currentVariables = await getGitLabVariables({
|
||||||
accessToken,
|
accessToken,
|
||||||
connection,
|
connection,
|
||||||
projectId,
|
scope,
|
||||||
|
resourceId,
|
||||||
targetEnvironment
|
targetEnvironment
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentVariableMap = new Map(currentVariables.map((v) => [v.key, v]));
|
const currentVariableMap = new Map(currentVariables.map((v) => [v.key, v]));
|
||||||
|
|
||||||
for (const [key, { value }] of Object.entries(secretMap)) {
|
for (const [key, { value }] of Object.entries(secretMap)) {
|
||||||
|
if (value?.length < 8 && destinationConfig.shouldMaskSecrets) {
|
||||||
|
throw new SecretSyncError({
|
||||||
|
message: `Secret ${key} is too short to be masked. GitLab requires a minimum of 8 characters for masked secrets.`,
|
||||||
|
secretKey: key
|
||||||
|
});
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const existingVariable = currentVariableMap.get(key);
|
const existingVariable = currentVariableMap.get(key);
|
||||||
|
|
||||||
@@ -279,17 +327,14 @@ export const GitLabSyncFns = {
|
|||||||
await updateGitLabVariable({
|
await updateGitLabVariable({
|
||||||
accessToken,
|
accessToken,
|
||||||
connection,
|
connection,
|
||||||
projectId,
|
scope,
|
||||||
|
resourceId,
|
||||||
key,
|
key,
|
||||||
variable: {
|
variable: {
|
||||||
value,
|
value,
|
||||||
variable_type: existingVariable.variable_type,
|
environment_scope: targetEnvironment,
|
||||||
environment_scope: targetEnvironment || existingVariable.environment_scope,
|
|
||||||
protected: destinationConfig.shouldProtectSecrets ?? existingVariable.protected,
|
protected: destinationConfig.shouldProtectSecrets ?? existingVariable.protected,
|
||||||
...(!existingVariable.masked && destinationConfig.shouldMaskSecrets && { masked: value?.length > 8 }),
|
masked: existingVariable.masked || destinationConfig.shouldMaskSecrets
|
||||||
...(!existingVariable.hidden &&
|
|
||||||
destinationConfig.shouldHideSecrets && { masked_and_hidden: value?.length > 8 }),
|
|
||||||
description: existingVariable.description ?? undefined
|
|
||||||
},
|
},
|
||||||
targetEnvironment
|
targetEnvironment
|
||||||
});
|
});
|
||||||
@@ -298,15 +343,16 @@ export const GitLabSyncFns = {
|
|||||||
await createGitLabVariable({
|
await createGitLabVariable({
|
||||||
accessToken,
|
accessToken,
|
||||||
connection,
|
connection,
|
||||||
projectId,
|
scope,
|
||||||
|
resourceId,
|
||||||
variable: {
|
variable: {
|
||||||
key,
|
key,
|
||||||
value,
|
value,
|
||||||
variable_type: "env_var",
|
variable_type: "env_var",
|
||||||
environment_scope: targetEnvironment || "*",
|
environment_scope: targetEnvironment || "*",
|
||||||
protected: destinationConfig.shouldProtectSecrets || false,
|
protected: destinationConfig.shouldProtectSecrets || false,
|
||||||
masked: value?.length > 8 ? destinationConfig.shouldMaskSecrets || false : false,
|
masked: destinationConfig.shouldMaskSecrets || false,
|
||||||
masked_and_hidden: value?.length > 8 ? destinationConfig.shouldHideSecrets || false : false
|
masked_and_hidden: destinationConfig.shouldHideSecrets || false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -323,13 +369,14 @@ export const GitLabSyncFns = {
|
|||||||
try {
|
try {
|
||||||
const shouldDelete =
|
const shouldDelete =
|
||||||
matchesSchema(variable.key, environment?.slug || "", secretSync.syncOptions.keySchema) &&
|
matchesSchema(variable.key, environment?.slug || "", secretSync.syncOptions.keySchema) &&
|
||||||
!(variable.key in secretMap);
|
variable.key in secretMap;
|
||||||
|
|
||||||
if (shouldDelete) {
|
if (shouldDelete) {
|
||||||
await deleteGitLabVariable({
|
await deleteGitLabVariable({
|
||||||
accessToken,
|
accessToken,
|
||||||
connection,
|
connection,
|
||||||
projectId,
|
scope,
|
||||||
|
resourceId,
|
||||||
key: variable.key,
|
key: variable.key,
|
||||||
targetEnvironment
|
targetEnvironment
|
||||||
});
|
});
|
||||||
@@ -359,19 +406,30 @@ export const GitLabSyncFns = {
|
|||||||
{ appConnectionDAL, kmsService }: TGitLabSyncFactoryDeps
|
{ appConnectionDAL, kmsService }: TGitLabSyncFactoryDeps
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const { connection, destinationConfig } = secretSync;
|
const { connection, destinationConfig } = secretSync;
|
||||||
|
const { scope, targetEnvironment } = destinationConfig;
|
||||||
|
|
||||||
const { projectId, targetEnvironment } = destinationConfig;
|
const resourceId = scope === GitLabSyncScope.Project ? destinationConfig.projectId : destinationConfig.groupId;
|
||||||
|
|
||||||
const accessToken = await getValidAccessToken(connection, appConnectionDAL, kmsService);
|
const accessToken = await getValidAccessToken(connection, appConnectionDAL, kmsService);
|
||||||
|
|
||||||
|
const allVariables = await getGitLabVariables({
|
||||||
|
accessToken,
|
||||||
|
connection,
|
||||||
|
scope,
|
||||||
|
resourceId,
|
||||||
|
targetEnvironment
|
||||||
|
});
|
||||||
|
|
||||||
for (const key of Object.keys(secretMap)) {
|
for (const key of Object.keys(secretMap)) {
|
||||||
try {
|
try {
|
||||||
await deleteGitLabVariable({
|
await deleteGitLabVariable({
|
||||||
accessToken,
|
accessToken,
|
||||||
connection,
|
connection,
|
||||||
projectId,
|
scope,
|
||||||
|
resourceId,
|
||||||
key,
|
key,
|
||||||
targetEnvironment
|
targetEnvironment,
|
||||||
|
allVariables
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new SecretSyncError({
|
throw new SecretSyncError({
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { GitLabSyncScope } from "./gitlab-sync-enums";
|
|||||||
|
|
||||||
const GitLabSyncDestinationConfigSchema = z.discriminatedUnion("scope", [
|
const GitLabSyncDestinationConfigSchema = z.discriminatedUnion("scope", [
|
||||||
z.object({
|
z.object({
|
||||||
scope: z.literal(GitLabSyncScope.Individual).describe(SecretSyncs.DESTINATION_CONFIG.GITLAB.scope),
|
scope: z.literal(GitLabSyncScope.Project).describe(SecretSyncs.DESTINATION_CONFIG.GITLAB.scope),
|
||||||
projectId: z.string().min(1, "Project ID is required").describe(SecretSyncs.DESTINATION_CONFIG.GITLAB.projectId),
|
projectId: z.string().min(1, "Project ID is required").describe(SecretSyncs.DESTINATION_CONFIG.GITLAB.projectId),
|
||||||
projectName: z
|
projectName: z
|
||||||
.string()
|
.string()
|
||||||
@@ -44,11 +44,7 @@ const GitLabSyncDestinationConfigSchema = z.discriminatedUnion("scope", [
|
|||||||
z.object({
|
z.object({
|
||||||
scope: z.literal(GitLabSyncScope.Group).describe(SecretSyncs.DESTINATION_CONFIG.GITLAB.scope),
|
scope: z.literal(GitLabSyncScope.Group).describe(SecretSyncs.DESTINATION_CONFIG.GITLAB.scope),
|
||||||
groupId: z.string().min(1, "Group ID is required").describe(SecretSyncs.DESTINATION_CONFIG.GITLAB.groupId),
|
groupId: z.string().min(1, "Group ID is required").describe(SecretSyncs.DESTINATION_CONFIG.GITLAB.groupId),
|
||||||
projectId: z.string().min(1, "Project ID is required").describe(SecretSyncs.DESTINATION_CONFIG.GITLAB.projectId),
|
groupName: z.string().min(1, "Group name is required").describe(SecretSyncs.DESTINATION_CONFIG.GITLAB.groupName),
|
||||||
projectName: z
|
|
||||||
.string()
|
|
||||||
.min(1, "Project name is required")
|
|
||||||
.describe(SecretSyncs.DESTINATION_CONFIG.GITLAB.projectName),
|
|
||||||
targetEnvironment: z
|
targetEnvironment: z
|
||||||
.string()
|
.string()
|
||||||
.optional()
|
.optional()
|
||||||
|
|||||||
@@ -15,13 +15,9 @@ export type TGitLabSyncWithCredentials = TGitLabSync & {
|
|||||||
export type TGitLabVariable = {
|
export type TGitLabVariable = {
|
||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
variable_type: "env_var" | "file";
|
|
||||||
protected: boolean;
|
protected: boolean;
|
||||||
masked: boolean;
|
masked: boolean;
|
||||||
hidden: boolean;
|
environmentScope?: string;
|
||||||
raw: boolean;
|
|
||||||
environment_scope: string;
|
|
||||||
description: string | null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TGitLabVariableCreate = {
|
export type TGitLabVariableCreate = {
|
||||||
|
|||||||
@@ -489,10 +489,10 @@ When set, all visits to the Infisical login page will automatically redirect use
|
|||||||
<Accordion title="Gitlab">
|
<Accordion title="Gitlab">
|
||||||
Follow detailed guide to configure [GitLab SSO](/documentation/platform/sso/gitlab)
|
Follow detailed guide to configure [GitLab SSO](/documentation/platform/sso/gitlab)
|
||||||
|
|
||||||
<ParamField query="CLIENT_ID_GITLAB_LOGIN" type="string" default="none" optional>
|
<ParamField query="INF_APP_CONNECTION_GITLAB_OAUTH_CLIENT_ID" type="string" default="none" optional>
|
||||||
OAuth2 client ID for GitLab login
|
OAuth2 client ID for GitLab login
|
||||||
</ParamField>
|
</ParamField>
|
||||||
<ParamField query="CLIENT_SECRET_GITLAB_LOGIN" type="string" default="none" optional>
|
<ParamField query="INF_APP_CONNECTION_GITLAB_OAUTH_CLIENT_SECRET" type="string" default="none" optional>
|
||||||
OAuth2 client secret for GitLab login
|
OAuth2 client secret for GitLab login
|
||||||
</ParamField>
|
</ParamField>
|
||||||
<ParamField query="URL_GITLAB_LOGIN" type="string" default="https://gitlab.com" optional>
|
<ParamField query="URL_GITLAB_LOGIN" type="string" default="https://gitlab.com" optional>
|
||||||
|
|||||||
+50
-44
@@ -68,7 +68,6 @@ export const GitLabSyncFields = () => {
|
|||||||
|
|
||||||
const connectionId = useWatch({ name: "connection.id", control });
|
const connectionId = useWatch({ name: "connection.id", control });
|
||||||
const scope = useWatch({ name: "destinationConfig.scope", control });
|
const scope = useWatch({ name: "destinationConfig.scope", control });
|
||||||
const selectedGroup = useWatch({ name: "destinationConfig.groupId", control });
|
|
||||||
const shouldMaskSecrets = useWatch({ name: "destinationConfig.shouldMaskSecrets", control });
|
const shouldMaskSecrets = useWatch({ name: "destinationConfig.shouldMaskSecrets", control });
|
||||||
|
|
||||||
const { data: groups, isLoading: isGroupsLoading } = useGitlabConnectionListGroups(connectionId, {
|
const { data: groups, isLoading: isGroupsLoading } = useGitlabConnectionListGroups(connectionId, {
|
||||||
@@ -77,7 +76,6 @@ export const GitLabSyncFields = () => {
|
|||||||
|
|
||||||
const { data: projects, isLoading: isProjectsLoading } = useGitlabConnectionListProjects(
|
const { data: projects, isLoading: isProjectsLoading } = useGitlabConnectionListProjects(
|
||||||
connectionId,
|
connectionId,
|
||||||
selectedGroup,
|
|
||||||
{
|
{
|
||||||
enabled: Boolean(connectionId)
|
enabled: Boolean(connectionId)
|
||||||
}
|
}
|
||||||
@@ -90,14 +88,15 @@ export const GitLabSyncFields = () => {
|
|||||||
setValue("destinationConfig.projectId", "");
|
setValue("destinationConfig.projectId", "");
|
||||||
setValue("destinationConfig.projectName", "");
|
setValue("destinationConfig.projectName", "");
|
||||||
setValue("destinationConfig.groupId", "");
|
setValue("destinationConfig.groupId", "");
|
||||||
setValue("destinationConfig.scope", GitlabSyncScope.Individual);
|
setValue("destinationConfig.groupName", "");
|
||||||
|
setValue("destinationConfig.scope", GitlabSyncScope.Project);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
name="destinationConfig.scope"
|
name="destinationConfig.scope"
|
||||||
control={control}
|
control={control}
|
||||||
defaultValue={GitlabSyncScope.Individual}
|
defaultValue={GitlabSyncScope.Project}
|
||||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
<FormControl errorText={error?.message} isError={Boolean(error?.message)} label="Scope">
|
<FormControl errorText={error?.message} isError={Boolean(error?.message)} label="Scope">
|
||||||
<Select
|
<Select
|
||||||
@@ -107,6 +106,7 @@ export const GitLabSyncFields = () => {
|
|||||||
setValue("destinationConfig.projectId", "");
|
setValue("destinationConfig.projectId", "");
|
||||||
setValue("destinationConfig.projectName", "");
|
setValue("destinationConfig.projectName", "");
|
||||||
setValue("destinationConfig.groupId", "");
|
setValue("destinationConfig.groupId", "");
|
||||||
|
setValue("destinationConfig.groupName", "");
|
||||||
}}
|
}}
|
||||||
className="w-full border border-mineshaft-500 capitalize"
|
className="w-full border border-mineshaft-500 capitalize"
|
||||||
position="popper"
|
position="popper"
|
||||||
@@ -151,6 +151,10 @@ export const GitLabSyncFields = () => {
|
|||||||
value={groups?.find((group) => group.id === value) ?? null}
|
value={groups?.find((group) => group.id === value) ?? null}
|
||||||
onChange={(option) => {
|
onChange={(option) => {
|
||||||
onChange((option as SingleValue<TGitLabGroup>)?.id ?? "");
|
onChange((option as SingleValue<TGitLabGroup>)?.id ?? "");
|
||||||
|
setValue(
|
||||||
|
"destinationConfig.groupName",
|
||||||
|
(option as SingleValue<TGitLabGroup>)?.name ?? ""
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
options={groups}
|
options={groups}
|
||||||
placeholder="Select a group..."
|
placeholder="Select a group..."
|
||||||
@@ -162,46 +166,48 @@ export const GitLabSyncFields = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Controller
|
{scope === GitlabSyncScope.Project && (
|
||||||
name="destinationConfig.projectId"
|
<Controller
|
||||||
control={control}
|
name="destinationConfig.projectId"
|
||||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
control={control}
|
||||||
<FormControl
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
isError={Boolean(error)}
|
<FormControl
|
||||||
errorText={error?.message}
|
isError={Boolean(error)}
|
||||||
label="GitLab Project"
|
errorText={error?.message}
|
||||||
helperText={
|
label="GitLab Project"
|
||||||
<Tooltip
|
helperText={
|
||||||
className="max-w-md"
|
<Tooltip
|
||||||
content="Ensure the project exists in the connection's GitLab instance URL and the connection has access to it."
|
className="max-w-md"
|
||||||
>
|
content="Ensure the project exists in the connection's GitLab instance URL and the connection has access to it."
|
||||||
<div>
|
>
|
||||||
<span>Don't see the project you're looking for?</span>{" "}
|
<div>
|
||||||
<FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-400" />
|
<span>Don't see the project you're looking for?</span>{" "}
|
||||||
</div>
|
<FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-400" />
|
||||||
</Tooltip>
|
</div>
|
||||||
}
|
</Tooltip>
|
||||||
>
|
}
|
||||||
<FilterableSelect
|
>
|
||||||
menuPlacement="top"
|
<FilterableSelect
|
||||||
isLoading={isProjectsLoading && Boolean(connectionId)}
|
menuPlacement="top"
|
||||||
isDisabled={!connectionId || (scope === GitlabSyncScope.Group && !selectedGroup)}
|
isLoading={isProjectsLoading && Boolean(connectionId)}
|
||||||
value={projects?.find((project) => project.id === value) ?? null}
|
isDisabled={!connectionId}
|
||||||
onChange={(option) => {
|
value={projects?.find((project) => project.id === value) ?? null}
|
||||||
onChange((option as SingleValue<TGitLabProject>)?.id ?? "");
|
onChange={(option) => {
|
||||||
setValue(
|
onChange((option as SingleValue<TGitLabProject>)?.id ?? "");
|
||||||
"destinationConfig.projectName",
|
setValue(
|
||||||
(option as SingleValue<TGitLabProject>)?.name ?? ""
|
"destinationConfig.projectName",
|
||||||
);
|
(option as SingleValue<TGitLabProject>)?.name ?? ""
|
||||||
}}
|
);
|
||||||
options={projects}
|
}}
|
||||||
placeholder="Select a project..."
|
options={projects}
|
||||||
getOptionLabel={(option) => option.name}
|
placeholder="Select a project..."
|
||||||
getOptionValue={(option) => option.id}
|
getOptionLabel={(option) => option.name}
|
||||||
/>
|
getOptionValue={(option) => option.id}
|
||||||
</FormControl>
|
/>
|
||||||
)}
|
</FormControl>
|
||||||
/>
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
|
|||||||
+2
-3
@@ -9,7 +9,7 @@ export const GitlabSyncDestinationSchema = BaseSecretSyncSchema().merge(
|
|||||||
destination: z.literal(SecretSync.GitLab),
|
destination: z.literal(SecretSync.GitLab),
|
||||||
destinationConfig: z.discriminatedUnion("scope", [
|
destinationConfig: z.discriminatedUnion("scope", [
|
||||||
z.object({
|
z.object({
|
||||||
scope: z.literal(GitlabSyncScope.Individual),
|
scope: z.literal(GitlabSyncScope.Project),
|
||||||
projectId: z.string().trim().min(1, "Project ID required"),
|
projectId: z.string().trim().min(1, "Project ID required"),
|
||||||
projectName: z.string().trim().min(1, "Project name required"),
|
projectName: z.string().trim().min(1, "Project name required"),
|
||||||
targetEnvironment: z.string().optional(),
|
targetEnvironment: z.string().optional(),
|
||||||
@@ -19,10 +19,9 @@ export const GitlabSyncDestinationSchema = BaseSecretSyncSchema().merge(
|
|||||||
}),
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
scope: z.literal(GitlabSyncScope.Group),
|
scope: z.literal(GitlabSyncScope.Group),
|
||||||
projectId: z.string().trim().min(1, "Project ID required"),
|
|
||||||
projectName: z.string().trim().min(1, "Project name required"),
|
|
||||||
targetEnvironment: z.string().optional(),
|
targetEnvironment: z.string().optional(),
|
||||||
groupId: z.string().trim().min(1, "Group ID required"),
|
groupId: z.string().trim().min(1, "Group ID required"),
|
||||||
|
groupName: z.string().trim().min(1, "Group name required"),
|
||||||
shouldProtectSecrets: z.boolean().optional().default(false),
|
shouldProtectSecrets: z.boolean().optional().default(false),
|
||||||
shouldMaskSecrets: z.boolean().optional().default(false),
|
shouldMaskSecrets: z.boolean().optional().default(false),
|
||||||
shouldHideSecrets: z.boolean().optional().default(false)
|
shouldHideSecrets: z.boolean().optional().default(false)
|
||||||
|
|||||||
@@ -7,15 +7,14 @@ import { TGitLabGroup, TGitLabProject } from "./types";
|
|||||||
|
|
||||||
const gitlabConnectionKeys = {
|
const gitlabConnectionKeys = {
|
||||||
all: [...appConnectionKeys.all, "gitlab"] as const,
|
all: [...appConnectionKeys.all, "gitlab"] as const,
|
||||||
listProjects: (connectionId: string, group?: string) =>
|
listProjects: (connectionId: string) =>
|
||||||
[...gitlabConnectionKeys.all, "projects", connectionId, group] as const,
|
[...gitlabConnectionKeys.all, "projects", connectionId] as const,
|
||||||
listGroups: (connectionId: string) =>
|
listGroups: (connectionId: string) =>
|
||||||
[...gitlabConnectionKeys.all, "groups", connectionId] as const
|
[...gitlabConnectionKeys.all, "groups", connectionId] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGitlabConnectionListProjects = (
|
export const useGitlabConnectionListProjects = (
|
||||||
connectionId: string,
|
connectionId: string,
|
||||||
group?: string,
|
|
||||||
options?: Omit<
|
options?: Omit<
|
||||||
UseQueryOptions<
|
UseQueryOptions<
|
||||||
TGitLabProject[],
|
TGitLabProject[],
|
||||||
@@ -27,10 +26,10 @@ export const useGitlabConnectionListProjects = (
|
|||||||
>
|
>
|
||||||
) => {
|
) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: gitlabConnectionKeys.listProjects(connectionId, group),
|
queryKey: gitlabConnectionKeys.listProjects(connectionId),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await apiRequest.get<TGitLabProject[]>(
|
const { data } = await apiRequest.get<TGitLabProject[]>(
|
||||||
`/api/v1/app-connections/gitlab/${connectionId}/projects${group ? `?group=${group}` : ""}`
|
`/api/v1/app-connections/gitlab/${connectionId}/projects`
|
||||||
);
|
);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { SecretSync } from "@app/hooks/api/secretSyncs";
|
|||||||
import { TRootSecretSync } from "@app/hooks/api/secretSyncs/types/root-sync";
|
import { TRootSecretSync } from "@app/hooks/api/secretSyncs/types/root-sync";
|
||||||
|
|
||||||
export enum GitlabSyncScope {
|
export enum GitlabSyncScope {
|
||||||
Individual = "individual",
|
Project = "project",
|
||||||
Group = "group"
|
Group = "group"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ export type TGitlabSync = TRootSecretSync & {
|
|||||||
destination: SecretSync.GitLab;
|
destination: SecretSync.GitLab;
|
||||||
destinationConfig:
|
destinationConfig:
|
||||||
| {
|
| {
|
||||||
scope: GitlabSyncScope.Individual;
|
scope: GitlabSyncScope.Project;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
projectName: string;
|
projectName: string;
|
||||||
targetEnvironment?: string;
|
targetEnvironment?: string;
|
||||||
@@ -22,8 +22,7 @@ export type TGitlabSync = TRootSecretSync & {
|
|||||||
| {
|
| {
|
||||||
scope: GitlabSyncScope.Group;
|
scope: GitlabSyncScope.Group;
|
||||||
groupId: string;
|
groupId: string;
|
||||||
projectId: string;
|
groupName: string;
|
||||||
projectName: string;
|
|
||||||
targetEnvironment?: string;
|
targetEnvironment?: string;
|
||||||
shouldProtectSecrets?: boolean;
|
shouldProtectSecrets?: boolean;
|
||||||
shouldMaskSecrets?: boolean;
|
shouldMaskSecrets?: boolean;
|
||||||
|
|||||||
+1
-1
@@ -145,7 +145,7 @@ export const GitLabConnectionForm = ({ appConnection, onSubmit: formSubmit }: Pr
|
|||||||
oauthUrl.searchParams.set("client_id", oauthClientId);
|
oauthUrl.searchParams.set("client_id", oauthClientId);
|
||||||
oauthUrl.searchParams.set(
|
oauthUrl.searchParams.set(
|
||||||
"redirect_uri",
|
"redirect_uri",
|
||||||
`${window.location.origin}/integrations/gitlab/oauth2/callback`
|
`${window.location.origin}/organization/app-connections/gitlab/oauth/callback`
|
||||||
);
|
);
|
||||||
oauthUrl.searchParams.set("response_type", "code");
|
oauthUrl.searchParams.set("response_type", "code");
|
||||||
oauthUrl.searchParams.set("state", state);
|
oauthUrl.searchParams.set("state", state);
|
||||||
|
|||||||
+61
@@ -11,12 +11,14 @@ import {
|
|||||||
AzureDevOpsConnectionMethod,
|
AzureDevOpsConnectionMethod,
|
||||||
AzureKeyVaultConnectionMethod,
|
AzureKeyVaultConnectionMethod,
|
||||||
GitHubConnectionMethod,
|
GitHubConnectionMethod,
|
||||||
|
GitlabConnectionMethod,
|
||||||
TAzureAppConfigurationConnection,
|
TAzureAppConfigurationConnection,
|
||||||
TAzureClientSecretsConnection,
|
TAzureClientSecretsConnection,
|
||||||
TAzureDevOpsConnection,
|
TAzureDevOpsConnection,
|
||||||
TAzureKeyVaultConnection,
|
TAzureKeyVaultConnection,
|
||||||
TGitHubConnection,
|
TGitHubConnection,
|
||||||
TGitHubRadarConnection,
|
TGitHubRadarConnection,
|
||||||
|
TGitlabConnection,
|
||||||
useCreateAppConnection,
|
useCreateAppConnection,
|
||||||
useUpdateAppConnection
|
useUpdateAppConnection
|
||||||
} from "@app/hooks/api/appConnections";
|
} from "@app/hooks/api/appConnections";
|
||||||
@@ -25,6 +27,7 @@ import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
|||||||
type BaseFormData = {
|
type BaseFormData = {
|
||||||
returnUrl?: string;
|
returnUrl?: string;
|
||||||
connectionId?: string;
|
connectionId?: string;
|
||||||
|
isUpdate?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type GithubFormData = BaseFormData & Pick<TGitHubConnection, "name" | "method" | "description">;
|
type GithubFormData = BaseFormData & Pick<TGitHubConnection, "name" | "method" | "description">;
|
||||||
@@ -32,6 +35,8 @@ type GithubFormData = BaseFormData & Pick<TGitHubConnection, "name" | "method" |
|
|||||||
type GithubRadarFormData = BaseFormData &
|
type GithubRadarFormData = BaseFormData &
|
||||||
Pick<TGitHubRadarConnection, "name" | "method" | "description">;
|
Pick<TGitHubRadarConnection, "name" | "method" | "description">;
|
||||||
|
|
||||||
|
type GitlabFormData = BaseFormData & Pick<TGitlabConnection, "name" | "method" | "description">;
|
||||||
|
|
||||||
type AzureKeyVaultFormData = BaseFormData &
|
type AzureKeyVaultFormData = BaseFormData &
|
||||||
Pick<TAzureKeyVaultConnection, "name" | "method" | "description"> &
|
Pick<TAzureKeyVaultConnection, "name" | "method" | "description"> &
|
||||||
Pick<TAzureKeyVaultConnection["credentials"], "tenantId">;
|
Pick<TAzureKeyVaultConnection["credentials"], "tenantId">;
|
||||||
@@ -60,6 +65,7 @@ type AzureDevOpsFormData = BaseFormData &
|
|||||||
type FormDataMap = {
|
type FormDataMap = {
|
||||||
[AppConnection.GitHub]: GithubFormData & { app: AppConnection.GitHub };
|
[AppConnection.GitHub]: GithubFormData & { app: AppConnection.GitHub };
|
||||||
[AppConnection.GitHubRadar]: GithubRadarFormData & { app: AppConnection.GitHubRadar };
|
[AppConnection.GitHubRadar]: GithubRadarFormData & { app: AppConnection.GitHubRadar };
|
||||||
|
[AppConnection.Gitlab]: GitlabFormData & { app: AppConnection.Gitlab };
|
||||||
[AppConnection.AzureKeyVault]: AzureKeyVaultFormData & { app: AppConnection.AzureKeyVault };
|
[AppConnection.AzureKeyVault]: AzureKeyVaultFormData & { app: AppConnection.AzureKeyVault };
|
||||||
[AppConnection.AzureAppConfiguration]: AzureAppConfigurationFormData & {
|
[AppConnection.AzureAppConfiguration]: AzureAppConfigurationFormData & {
|
||||||
app: AppConnection.AzureAppConfiguration;
|
app: AppConnection.AzureAppConfiguration;
|
||||||
@@ -75,6 +81,7 @@ type FormDataMap = {
|
|||||||
const formDataStorageFieldMap: Partial<Record<AppConnection, string>> = {
|
const formDataStorageFieldMap: Partial<Record<AppConnection, string>> = {
|
||||||
[AppConnection.GitHub]: "githubConnectionFormData",
|
[AppConnection.GitHub]: "githubConnectionFormData",
|
||||||
[AppConnection.GitHubRadar]: "githubRadarConnectionFormData",
|
[AppConnection.GitHubRadar]: "githubRadarConnectionFormData",
|
||||||
|
[AppConnection.Gitlab]: "gitlabConnectionFormData",
|
||||||
[AppConnection.AzureKeyVault]: "azureKeyVaultConnectionFormData",
|
[AppConnection.AzureKeyVault]: "azureKeyVaultConnectionFormData",
|
||||||
[AppConnection.AzureAppConfiguration]: "azureAppConfigurationConnectionFormData",
|
[AppConnection.AzureAppConfiguration]: "azureAppConfigurationConnectionFormData",
|
||||||
[AppConnection.AzureClientSecrets]: "azureClientSecretsConnectionFormData",
|
[AppConnection.AzureClientSecrets]: "azureClientSecretsConnectionFormData",
|
||||||
@@ -133,6 +140,57 @@ export const OAuthCallbackPage = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleGitlab = useCallback(async () => {
|
||||||
|
const formData = getFormData(AppConnection.Gitlab);
|
||||||
|
if (formData === null) return null;
|
||||||
|
|
||||||
|
clearState(AppConnection.Gitlab);
|
||||||
|
|
||||||
|
const { connectionId, name, description, returnUrl, isUpdate } = formData;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (isUpdate && connectionId) {
|
||||||
|
await updateAppConnection.mutateAsync({
|
||||||
|
app: AppConnection.Gitlab,
|
||||||
|
connectionId,
|
||||||
|
credentials: {
|
||||||
|
code: code as string
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await createAppConnection.mutateAsync({
|
||||||
|
app: AppConnection.Gitlab,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
method: GitlabConnectionMethod.OAuth,
|
||||||
|
credentials: {
|
||||||
|
code: code as string
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
navigate({
|
||||||
|
to: returnUrl ?? "/organization/app-connections"
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
connectionId,
|
||||||
|
returnUrl,
|
||||||
|
appConnectionName: formData.app
|
||||||
|
};
|
||||||
|
} catch (err: any) {
|
||||||
|
createNotification({
|
||||||
|
title: `Failed to ${connectionId ? "update" : "add"} GitLab Connection`,
|
||||||
|
text: err?.message,
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
navigate({
|
||||||
|
to: returnUrl ?? "/organization/app-connections"
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleAzureKeyVault = useCallback(async () => {
|
const handleAzureKeyVault = useCallback(async () => {
|
||||||
const formData = getFormData(AppConnection.AzureKeyVault);
|
const formData = getFormData(AppConnection.AzureKeyVault);
|
||||||
if (formData === null) return null;
|
if (formData === null) return null;
|
||||||
@@ -458,11 +516,14 @@ export const OAuthCallbackPage = () => {
|
|||||||
(async () => {
|
(async () => {
|
||||||
let data: { connectionId?: string; returnUrl?: string; appConnectionName?: string } | null =
|
let data: { connectionId?: string; returnUrl?: string; appConnectionName?: string } | null =
|
||||||
null;
|
null;
|
||||||
|
console.log("appConnection", appConnection);
|
||||||
|
|
||||||
if (appConnection === AppConnection.GitHub) {
|
if (appConnection === AppConnection.GitHub) {
|
||||||
data = await handleGithub();
|
data = await handleGithub();
|
||||||
} else if (appConnection === AppConnection.GitHubRadar) {
|
} else if (appConnection === AppConnection.GitHubRadar) {
|
||||||
data = await handleGithubRadar();
|
data = await handleGithubRadar();
|
||||||
|
} else if (appConnection === AppConnection.Gitlab) {
|
||||||
|
data = await handleGitlab();
|
||||||
} else if (appConnection === AppConnection.AzureKeyVault) {
|
} else if (appConnection === AppConnection.AzureKeyVault) {
|
||||||
data = await handleAzureKeyVault();
|
data = await handleAzureKeyVault();
|
||||||
} else if (appConnection === AppConnection.AzureAppConfiguration) {
|
} else if (appConnection === AppConnection.AzureAppConfiguration) {
|
||||||
|
|||||||
+10
-2
@@ -5,6 +5,7 @@ import {
|
|||||||
GitHubSyncScope,
|
GitHubSyncScope,
|
||||||
GitHubSyncVisibility
|
GitHubSyncVisibility
|
||||||
} from "@app/hooks/api/secretSyncs/types/github-sync";
|
} from "@app/hooks/api/secretSyncs/types/github-sync";
|
||||||
|
import { GitlabSyncScope } from "@app/hooks/api/secretSyncs/types/gitlab-sync";
|
||||||
import { HumanitecSyncScope } from "@app/hooks/api/secretSyncs/types/humanitec-sync";
|
import { HumanitecSyncScope } from "@app/hooks/api/secretSyncs/types/humanitec-sync";
|
||||||
|
|
||||||
// This functional ensures parity across what is displayed in the destination column
|
// This functional ensures parity across what is displayed in the destination column
|
||||||
@@ -129,8 +130,15 @@ export const getSecretSyncDestinationColValues = (secretSync: TSecretSync) => {
|
|||||||
secondaryText = "App ID";
|
secondaryText = "App ID";
|
||||||
break;
|
break;
|
||||||
case SecretSync.GitLab:
|
case SecretSync.GitLab:
|
||||||
primaryText = destinationConfig.projectName;
|
if (destinationConfig.scope === GitlabSyncScope.Project) {
|
||||||
secondaryText = destinationConfig.projectId;
|
primaryText = destinationConfig.projectName;
|
||||||
|
secondaryText = destinationConfig.projectId;
|
||||||
|
} else if (destinationConfig.scope === GitlabSyncScope.Group) {
|
||||||
|
primaryText = destinationConfig.groupName;
|
||||||
|
secondaryText = destinationConfig.groupId;
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unhandled GitLab Scope Destination Col Values ${destination}`);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled Destination Col Values ${destination}`);
|
throw new Error(`Unhandled Destination Col Values ${destination}`);
|
||||||
|
|||||||
+21
-5
@@ -1,5 +1,5 @@
|
|||||||
import { GenericFieldLabel } from "@app/components/secret-syncs";
|
import { GenericFieldLabel } from "@app/components/secret-syncs";
|
||||||
import { TGitlabSync } from "@app/hooks/api/secretSyncs/types/gitlab-sync";
|
import { GitlabSyncScope, TGitlabSync } from "@app/hooks/api/secretSyncs/types/gitlab-sync";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
secretSync: TGitlabSync;
|
secretSync: TGitlabSync;
|
||||||
@@ -8,8 +8,6 @@ type Props = {
|
|||||||
export const GitLabSyncDestinationSection = ({ secretSync }: Props) => {
|
export const GitLabSyncDestinationSection = ({ secretSync }: Props) => {
|
||||||
const {
|
const {
|
||||||
destinationConfig: {
|
destinationConfig: {
|
||||||
projectName,
|
|
||||||
projectId,
|
|
||||||
targetEnvironment,
|
targetEnvironment,
|
||||||
shouldProtectSecrets,
|
shouldProtectSecrets,
|
||||||
shouldMaskSecrets,
|
shouldMaskSecrets,
|
||||||
@@ -19,8 +17,26 @@ export const GitLabSyncDestinationSection = ({ secretSync }: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<GenericFieldLabel label="Project Name">{projectName}</GenericFieldLabel>
|
{secretSync.destinationConfig.scope === GitlabSyncScope.Project && (
|
||||||
<GenericFieldLabel label="Project ID">{projectId}</GenericFieldLabel>
|
<>
|
||||||
|
<GenericFieldLabel label="Project Name">
|
||||||
|
{secretSync.destinationConfig.projectName}
|
||||||
|
</GenericFieldLabel>
|
||||||
|
<GenericFieldLabel label="Project ID">
|
||||||
|
{secretSync.destinationConfig.projectId}
|
||||||
|
</GenericFieldLabel>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{secretSync.destinationConfig.scope === GitlabSyncScope.Group && (
|
||||||
|
<>
|
||||||
|
<GenericFieldLabel label="Group Name">
|
||||||
|
{secretSync.destinationConfig.groupName}
|
||||||
|
</GenericFieldLabel>
|
||||||
|
<GenericFieldLabel label="Group ID">
|
||||||
|
{secretSync.destinationConfig.groupId}
|
||||||
|
</GenericFieldLabel>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{targetEnvironment && (
|
{targetEnvironment && (
|
||||||
<GenericFieldLabel label="Environment">{targetEnvironment}</GenericFieldLabel>
|
<GenericFieldLabel label="Environment">{targetEnvironment}</GenericFieldLabel>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user