mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Fix merge conflicts
This commit is contained in:
@@ -17,9 +17,9 @@ jobs:
|
||||
- name: 📦 Install dependencies to test all dependencies
|
||||
run: npm ci --only-production
|
||||
working-directory: backend
|
||||
- name: 🧪 Run tests
|
||||
run: npm run test:ci
|
||||
working-directory: backend
|
||||
# - name: 🧪 Run tests
|
||||
# run: npm run test:ci
|
||||
# working-directory: backend
|
||||
- name: Save commit hashes for tag
|
||||
id: commit
|
||||
uses: pr-mpt/actions-commit-hash@v2
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
INTEGRATION_BITBUCKET_API_URL,
|
||||
INTEGRATION_GCP_SECRET_MANAGER,
|
||||
INTEGRATION_NORTHFLANK_API_URL,
|
||||
INTEGRATION_QOVERY_API_URL,
|
||||
INTEGRATION_RAILWAY_API_URL,
|
||||
INTEGRATION_SET,
|
||||
INTEGRATION_VERCEL_API_URL,
|
||||
@@ -344,6 +345,362 @@ export const getIntegrationAuthVercelBranches = async (req: Request, res: Respon
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of Qovery Orgs for a specific user
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const getIntegrationAuthQoveryOrgs = async (req: Request, res: Response) => {
|
||||
const {
|
||||
params: { integrationAuthId }
|
||||
} = await validateRequest(reqValidator.GetIntegrationAuthQoveryOrgsV1, req);
|
||||
|
||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
||||
integrationAuthId: new ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
);
|
||||
|
||||
const { data } = await standardRequest.get(
|
||||
`${INTEGRATION_QOVERY_API_URL}/organization`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Token ${accessToken}`,
|
||||
"Accept": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
interface QoveryOrg {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
const orgs = data.results.map((a: QoveryOrg) => {
|
||||
return {
|
||||
name: a.name,
|
||||
orgId: a.id,
|
||||
};
|
||||
});
|
||||
|
||||
return res.status(200).send({
|
||||
orgs
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of Qovery Projects for a specific orgId
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const getIntegrationAuthQoveryProjects = async (req: Request, res: Response) => {
|
||||
const {
|
||||
params: { integrationAuthId },
|
||||
query: { orgId }
|
||||
} = await validateRequest(reqValidator.GetIntegrationAuthQoveryProjectsV1, req);
|
||||
|
||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
||||
integrationAuthId: new ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
);
|
||||
|
||||
interface Project {
|
||||
name: string;
|
||||
projectId: string;
|
||||
}
|
||||
|
||||
interface QoveryProject {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
let projects: Project[] = [];
|
||||
|
||||
if (orgId && orgId !== "") {
|
||||
const { data } = await standardRequest.get(
|
||||
`${INTEGRATION_QOVERY_API_URL}/organization/${orgId}/project`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Token ${accessToken}`,
|
||||
"Accept": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
projects = data.results.map((a: QoveryProject) => {
|
||||
return {
|
||||
name: a.name,
|
||||
projectId: a.id,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
projects
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of Qovery environments for project with id [projectId]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const getIntegrationAuthQoveryEnvironments = async (req: Request, res: Response) => {
|
||||
const {
|
||||
params: { integrationAuthId },
|
||||
query: { projectId }
|
||||
} = await validateRequest(reqValidator.GetIntegrationAuthQoveryEnvironmentsV1, req);
|
||||
|
||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
||||
integrationAuthId: new ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
);
|
||||
|
||||
interface Environment {
|
||||
name: string;
|
||||
environmentId: string;
|
||||
}
|
||||
|
||||
interface QoveryEnvironment {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
let environments: Environment[] = [];
|
||||
|
||||
if (projectId && projectId !== "" && projectId !== "none") { // TODO: fix
|
||||
const { data } = await standardRequest.get(
|
||||
`${INTEGRATION_QOVERY_API_URL}/project/${projectId}/environment`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Token ${accessToken}`,
|
||||
"Accept": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
environments = data.results.map((a: QoveryEnvironment) => {
|
||||
return {
|
||||
name: a.name,
|
||||
environmentId: a.id,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
environments
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of Qovery apps for environment with id [environmentId]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const getIntegrationAuthQoveryApps = async (req: Request, res: Response) => {
|
||||
const {
|
||||
params: { integrationAuthId },
|
||||
query: { environmentId }
|
||||
} = await validateRequest(reqValidator.GetIntegrationAuthQoveryScopesV1, req);
|
||||
|
||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
||||
integrationAuthId: new ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
);
|
||||
|
||||
interface App {
|
||||
name: string;
|
||||
appId: string;
|
||||
}
|
||||
|
||||
interface QoveryApp {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
let apps: App[] = [];
|
||||
|
||||
if (environmentId && environmentId !== "") {
|
||||
const { data } = await standardRequest.get(
|
||||
`${INTEGRATION_QOVERY_API_URL}/environment/${environmentId}/application`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Token ${accessToken}`,
|
||||
"Accept": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
apps = data.results.map((a: QoveryApp) => {
|
||||
return {
|
||||
name: a.name,
|
||||
appId: a.id,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
apps
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of Qovery containers for environment with id [environmentId]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const getIntegrationAuthQoveryContainers = async (req: Request, res: Response) => {
|
||||
const {
|
||||
params: { integrationAuthId },
|
||||
query: { environmentId }
|
||||
} = await validateRequest(reqValidator.GetIntegrationAuthQoveryScopesV1, req);
|
||||
|
||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
||||
integrationAuthId: new ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
);
|
||||
|
||||
interface Container {
|
||||
name: string;
|
||||
appId: string;
|
||||
}
|
||||
|
||||
interface QoveryContainer {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
let containers: Container[] = [];
|
||||
|
||||
if (environmentId && environmentId !== "") {
|
||||
const { data } = await standardRequest.get(
|
||||
`${INTEGRATION_QOVERY_API_URL}/environment/${environmentId}/container`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Token ${accessToken}`,
|
||||
"Accept": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
containers = data.results.map((a: QoveryContainer) => {
|
||||
return {
|
||||
name: a.name,
|
||||
appId: a.id,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
containers
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of Qovery jobs for environment with id [environmentId]
|
||||
* @param req
|
||||
* @param res
|
||||
*/
|
||||
export const getIntegrationAuthQoveryJobs = async (req: Request, res: Response) => {
|
||||
const {
|
||||
params: { integrationAuthId },
|
||||
query: { environmentId }
|
||||
} = await validateRequest(reqValidator.GetIntegrationAuthQoveryScopesV1, req);
|
||||
|
||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
||||
integrationAuthId: new ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
const { permission } = await getUserProjectPermissions(
|
||||
req.user._id,
|
||||
integrationAuth.workspace.toString()
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(
|
||||
ProjectPermissionActions.Read,
|
||||
ProjectPermissionSub.Integrations
|
||||
);
|
||||
|
||||
interface Job {
|
||||
name: string;
|
||||
appId: string;
|
||||
}
|
||||
|
||||
interface QoveryJob {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
let jobs: Job[] = [];
|
||||
|
||||
if (environmentId && environmentId !== "") {
|
||||
const { data } = await standardRequest.get(
|
||||
`${INTEGRATION_QOVERY_API_URL}/environment/${environmentId}/job`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Token ${accessToken}`,
|
||||
"Accept": "application/json",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
jobs = data.results.map((a: QoveryJob) => {
|
||||
return {
|
||||
name: a.name,
|
||||
appId: a.id,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
jobs
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return list of Railway environments for Railway project with
|
||||
* id [appId]
|
||||
@@ -713,11 +1070,12 @@ export const getIntegrationAuthNorthflankSecretGroups = async (req: Request, res
|
||||
*/
|
||||
export const getIntegrationAuthTeamCityBuildConfigs = async (req: Request, res: Response) => {
|
||||
const {
|
||||
params: { integrationAuthId, appId }
|
||||
params: { integrationAuthId },
|
||||
query: { appId }
|
||||
} = await validateRequest(reqValidator.GetIntegrationAuthTeamCityBuildConfigsV1, req);
|
||||
|
||||
|
||||
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
|
||||
const { integrationAuth } = await getIntegrationAuthAccessHelper({
|
||||
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
|
||||
integrationAuthId: new ObjectId(integrationAuthId)
|
||||
});
|
||||
|
||||
@@ -749,13 +1107,13 @@ export const getIntegrationAuthTeamCityBuildConfigs = async (req: Request, res:
|
||||
const {
|
||||
data: { buildType }
|
||||
} = await standardRequest.get<GetTeamCityBuildConfigsRes>(
|
||||
`${req.integrationAuth.url}/app/rest/buildTypes`,
|
||||
`${integrationAuth.url}/app/rest/buildTypes`,
|
||||
{
|
||||
params: {
|
||||
locator: `project:${appId}`
|
||||
},
|
||||
headers: {
|
||||
Authorization: `Bearer ${req.accessToken}`,
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: "application/json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ export const createIntegration = async (req: Request, res: Response) => {
|
||||
appId,
|
||||
owner,
|
||||
region,
|
||||
scope,
|
||||
targetService,
|
||||
targetServiceId,
|
||||
integrationAuthId,
|
||||
@@ -42,7 +43,7 @@ export const createIntegration = async (req: Request, res: Response) => {
|
||||
metadata
|
||||
}
|
||||
} = await validateRequest(reqValidator.CreateIntegrationV1, req);
|
||||
|
||||
|
||||
const integrationAuth = await IntegrationAuth.findById(integrationAuthId)
|
||||
.populate<{ workspace: IWorkspace }>("workspace")
|
||||
.select(
|
||||
@@ -90,6 +91,7 @@ export const createIntegration = async (req: Request, res: Response) => {
|
||||
owner,
|
||||
path,
|
||||
region,
|
||||
scope,
|
||||
secretPath,
|
||||
integration: integrationAuth.integration,
|
||||
integrationAuth: new Types.ObjectId(integrationAuthId),
|
||||
|
||||
@@ -15,6 +15,7 @@ import { IntegrationAuthMetadata } from "../models/integrationAuth/types";
|
||||
interface Update {
|
||||
workspace: string;
|
||||
integration: string;
|
||||
url?: string;
|
||||
teamId?: string;
|
||||
accountId?: string;
|
||||
metadata?: IntegrationAuthMetadata
|
||||
@@ -63,6 +64,10 @@ export const handleOAuthExchangeHelper = async ({
|
||||
workspace: workspaceId,
|
||||
integration
|
||||
};
|
||||
|
||||
if (res.url) {
|
||||
update.url = res.url;
|
||||
}
|
||||
|
||||
switch (integration) {
|
||||
case INTEGRATION_VERCEL:
|
||||
@@ -160,7 +165,7 @@ export const getIntegrationAuthAccessHelper = async ({
|
||||
let accessId;
|
||||
let accessToken;
|
||||
const integrationAuth = await IntegrationAuth.findById(integrationAuthId).select(
|
||||
"workspace integration +accessCiphertext +accessIV +accessTag +accessExpiresAt +refreshCiphertext +refreshIV +refreshTag +accessIdCiphertext +accessIdIV +accessIdTag metadata teamId"
|
||||
"workspace integration +accessCiphertext +accessIV +accessTag +accessExpiresAt +refreshCiphertext +refreshIV +refreshTag +accessIdCiphertext +accessIdIV +accessIdTag metadata teamId url"
|
||||
);
|
||||
|
||||
if (!integrationAuth)
|
||||
|
||||
@@ -423,6 +423,7 @@ const exchangeCodeGitlab = async ({
|
||||
accessToken: res.access_token,
|
||||
refreshToken: res.refresh_token,
|
||||
accessExpiresAt,
|
||||
url
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@ import {
|
||||
INTEGRATION_NETLIFY_API_URL,
|
||||
INTEGRATION_NORTHFLANK,
|
||||
INTEGRATION_NORTHFLANK_API_URL,
|
||||
INTEGRATION_QOVERY,
|
||||
INTEGRATION_QOVERY_API_URL,
|
||||
INTEGRATION_RAILWAY,
|
||||
INTEGRATION_RAILWAY_API_URL,
|
||||
INTEGRATION_RENDER,
|
||||
@@ -219,6 +221,13 @@ const syncSecrets = async ({
|
||||
accessToken
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_QOVERY:
|
||||
await syncSecretsQovery({
|
||||
integration,
|
||||
secrets,
|
||||
accessToken
|
||||
});
|
||||
break;
|
||||
case INTEGRATION_TERRAFORM_CLOUD:
|
||||
await syncSecretsTerraformCloud({
|
||||
integration,
|
||||
@@ -941,7 +950,11 @@ const syncSecretsVercel = async ({
|
||||
? {
|
||||
teamId: integrationAuth.teamId
|
||||
}
|
||||
: {})
|
||||
: {}),
|
||||
...(integration?.path
|
||||
? {
|
||||
gitBranch: integration?.path
|
||||
} : {})
|
||||
};
|
||||
|
||||
const vercelSecrets: VercelSecret[] = (
|
||||
@@ -960,7 +973,7 @@ const syncSecretsVercel = async ({
|
||||
|
||||
if (
|
||||
integration.targetEnvironment === "preview" &&
|
||||
integration.path &&
|
||||
secret.gitBranch &&
|
||||
integration.path !== secret.gitBranch
|
||||
) {
|
||||
// case: secret on preview environment does not have same target git branch
|
||||
@@ -969,7 +982,7 @@ const syncSecretsVercel = async ({
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
const res: { [key: string]: VercelSecret } = {};
|
||||
|
||||
for await (const vercelSecret of vercelSecrets) {
|
||||
@@ -2126,6 +2139,97 @@ const syncSecretsCheckly = async ({
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sync/push [secrets] to Qovery app
|
||||
* @param {Object} obj
|
||||
* @param {IIntegration} obj.integration - integration details
|
||||
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
|
||||
* @param {String} obj.accessToken - access token for Qovery integration
|
||||
*/
|
||||
const syncSecretsQovery = async ({
|
||||
integration,
|
||||
secrets,
|
||||
accessToken
|
||||
}: {
|
||||
integration: IIntegration;
|
||||
secrets: Record<string, { value: string; comment?: string }>;
|
||||
accessToken: string;
|
||||
}) => {
|
||||
|
||||
const getSecretsRes = (
|
||||
await standardRequest.get(`${INTEGRATION_QOVERY_API_URL}/${integration.scope}/${integration.appId}/environmentVariable`, {
|
||||
headers: {
|
||||
Authorization: `Token ${accessToken}`,
|
||||
"Accept-Encoding": "application/json"
|
||||
}
|
||||
})
|
||||
).data.results.reduce(
|
||||
(obj: any, secret: any) => ({
|
||||
...obj,
|
||||
[secret.key]: {"id": secret.id, "value": secret.value}
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
// add secrets
|
||||
for await (const key of Object.keys(secrets)) {
|
||||
if (!(key in getSecretsRes)) {
|
||||
// case: secret does not exist in qovery
|
||||
// -> add secret
|
||||
await standardRequest.post(
|
||||
`${INTEGRATION_QOVERY_API_URL}/${integration.scope}/${integration.appId}/environmentVariable`,
|
||||
{
|
||||
key,
|
||||
value: secrets[key].value
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Token ${accessToken}`,
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// case: secret exists in qovery
|
||||
// -> update/set secret
|
||||
|
||||
if (secrets[key].value !== getSecretsRes[key].value) {
|
||||
await standardRequest.put(
|
||||
`${INTEGRATION_QOVERY_API_URL}/${integration.scope}/${integration.appId}/environmentVariable/${getSecretsRes[key].id}`,
|
||||
{
|
||||
key,
|
||||
value: secrets[key].value
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Token ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This one is dangerous because there might be a lot of qovery-specific secrets
|
||||
|
||||
// for await (const key of Object.keys(getSecretsRes)) {
|
||||
// if (!(key in secrets)) {
|
||||
// console.log(3)
|
||||
// // delete secret
|
||||
// await standardRequest.delete(`${INTEGRATION_QOVERY_API_URL}/application/${integration.appId}/environmentVariable/${getSecretsRes[key].id}`, {
|
||||
// headers: {
|
||||
// Authorization: `Token ${accessToken}`,
|
||||
// Accept: "application/json",
|
||||
// "X-Qovery-Account": integration.appId
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
};
|
||||
|
||||
/**
|
||||
* Sync/push [secrets] to Terraform Cloud project with id [integration.appId]
|
||||
* @param {Object} obj
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
INTEGRATION_LARAVELFORGE,
|
||||
INTEGRATION_NETLIFY,
|
||||
INTEGRATION_NORTHFLANK,
|
||||
INTEGRATION_QOVERY,
|
||||
INTEGRATION_RAILWAY,
|
||||
INTEGRATION_RENDER,
|
||||
INTEGRATION_SUPABASE,
|
||||
@@ -45,6 +46,7 @@ export interface IIntegration {
|
||||
targetServiceId: string;
|
||||
path: string;
|
||||
region: string;
|
||||
scope: string;
|
||||
secretPath: string;
|
||||
integration:
|
||||
| "azure-key-vault"
|
||||
@@ -63,6 +65,7 @@ export interface IIntegration {
|
||||
| "travisci"
|
||||
| "supabase"
|
||||
| "checkly"
|
||||
| "qovery"
|
||||
| "terraform-cloud"
|
||||
| "teamcity"
|
||||
| "hashicorp-vault"
|
||||
@@ -119,11 +122,13 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
},
|
||||
targetService: {
|
||||
// railway-specific service
|
||||
// qovery-specific project
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
targetServiceId: {
|
||||
// railway-specific service
|
||||
// qovery specific project
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
@@ -143,6 +148,11 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
scope: {
|
||||
// qovery-specific scope
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
integration: {
|
||||
type: String,
|
||||
enum: [
|
||||
@@ -162,6 +172,7 @@ const integrationSchema = new Schema<IIntegration>(
|
||||
INTEGRATION_TRAVISCI,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_CHECKLY,
|
||||
INTEGRATION_QOVERY,
|
||||
INTEGRATION_TERRAFORM_CLOUD,
|
||||
INTEGRATION_TEAMCITY,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
|
||||
// TODO: in the future separate metadata
|
||||
// into distinct types by integration
|
||||
export type Metadata = {
|
||||
secretPrefix?: string;
|
||||
secretSuffix?: string;
|
||||
|
||||
@@ -52,6 +52,7 @@ import {
|
||||
| "aws-parameter-store"
|
||||
| "aws-secret-manager"
|
||||
| "checkly"
|
||||
| "qovery"
|
||||
| "cloudflare-pages"
|
||||
| "codefresh"
|
||||
| "digital-ocean-app-platform"
|
||||
|
||||
@@ -60,6 +60,54 @@ router.get(
|
||||
integrationAuthController.getIntegrationAuthVercelBranches
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:integrationAuthId/qovery/orgs",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
}),
|
||||
integrationAuthController.getIntegrationAuthQoveryOrgs
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:integrationAuthId/qovery/projects",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
}),
|
||||
integrationAuthController.getIntegrationAuthQoveryProjects
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:integrationAuthId/qovery/environments",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
}),
|
||||
integrationAuthController.getIntegrationAuthQoveryEnvironments
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:integrationAuthId/qovery/apps",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
}),
|
||||
integrationAuthController.getIntegrationAuthQoveryApps
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:integrationAuthId/qovery/containers",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
}),
|
||||
integrationAuthController.getIntegrationAuthQoveryContainers
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:integrationAuthId/qovery/jobs",
|
||||
requireAuth({
|
||||
acceptedAuthModes: [AuthMode.JWT]
|
||||
}),
|
||||
integrationAuthController.getIntegrationAuthQoveryJobs
|
||||
);
|
||||
|
||||
router.get(
|
||||
"/:integrationAuthId/railway/environments",
|
||||
requireAuth({
|
||||
|
||||
@@ -21,9 +21,8 @@ import {
|
||||
} from "../config";
|
||||
import { getSSOConfigHelper } from "../ee/helpers/organizations";
|
||||
import { InternalServerError, OrganizationNotFoundError } from "./errors";
|
||||
import { ACCEPTED, INVITED, MEMBER } from "../variables";
|
||||
import { ACCEPTED, INTEGRATION_GITHUB_API_URL, INVITED, MEMBER } from "../variables";
|
||||
import { getSiteURL } from "../config";
|
||||
|
||||
import { standardRequest } from "../config/request";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
@@ -146,11 +145,27 @@ const initializePassport = async () => {
|
||||
clientID: clientIdGitHubLogin,
|
||||
clientSecret: clientSecretGitHubLogin,
|
||||
callbackURL: "/api/v1/sso/github",
|
||||
scope: [ 'user:email' ]
|
||||
scope: ["user:email"]
|
||||
},
|
||||
async (req : express.Request, accessToken : any, refreshToken : any, profile : any, done : any) => {
|
||||
interface GitHubEmail {
|
||||
email: string;
|
||||
primary: boolean;
|
||||
verified: boolean;
|
||||
visibility: null | string;
|
||||
}
|
||||
|
||||
const email = profile.emails[0].value;
|
||||
const { data }: { data: GitHubEmail[] } = await standardRequest.get(
|
||||
`${INTEGRATION_GITHUB_API_URL}/user/emails`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const primaryEmail = data.filter((gitHubEmail: GitHubEmail) => gitHubEmail.primary)[0];
|
||||
const email = primaryEmail.email;
|
||||
|
||||
let user = await User.findOne({
|
||||
email
|
||||
|
||||
@@ -80,13 +80,14 @@ export const CreateIntegrationV1 = z.object({
|
||||
owner: z.string().trim().optional(),
|
||||
path: z.string().trim().optional(),
|
||||
region: z.string().trim().optional(),
|
||||
scope: z.string().trim().optional(),
|
||||
metadata: z.object({
|
||||
secretPrefix: z.string().optional(),
|
||||
secretSuffix: z.string().optional(),
|
||||
secretGCPLabel: z.object({
|
||||
labelName: z.string(),
|
||||
labelValue: z.string()
|
||||
}).optional()
|
||||
}).optional(),
|
||||
}).optional()
|
||||
})
|
||||
});
|
||||
|
||||
@@ -117,6 +117,39 @@ export const GetIntegrationAuthVercelBranchesV1 = z.object({
|
||||
})
|
||||
});
|
||||
|
||||
export const GetIntegrationAuthQoveryOrgsV1 = z.object({
|
||||
params: z.object({
|
||||
integrationAuthId: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const GetIntegrationAuthQoveryProjectsV1 = z.object({
|
||||
params: z.object({
|
||||
integrationAuthId: z.string().trim()
|
||||
}),
|
||||
query: z.object({
|
||||
orgId: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const GetIntegrationAuthQoveryEnvironmentsV1 = z.object({
|
||||
params: z.object({
|
||||
integrationAuthId: z.string().trim()
|
||||
}),
|
||||
query: z.object({
|
||||
projectId: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const GetIntegrationAuthQoveryScopesV1 = z.object({
|
||||
params: z.object({
|
||||
integrationAuthId: z.string().trim()
|
||||
}),
|
||||
query: z.object({
|
||||
environmentId: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const GetIntegrationAuthRailwayEnvironmentsV1 = z.object({
|
||||
params: z.object({
|
||||
integrationAuthId: z.string().trim()
|
||||
@@ -157,9 +190,11 @@ export const DeleteIntegrationAuthV1 = z.object({
|
||||
});
|
||||
|
||||
export const GetIntegrationAuthTeamCityBuildConfigsV1 = z.object({
|
||||
params:z.object({
|
||||
appId:z.string().trim().optional(),
|
||||
params: z.object({
|
||||
integrationAuthId:z.string().trim()
|
||||
}),
|
||||
query: z.object({
|
||||
appId:z.string().trim()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -274,7 +274,7 @@ export const ToggleAutoCapitalizationV2 = z.object({
|
||||
workspaceId: z.string().trim()
|
||||
}),
|
||||
body: z.object({
|
||||
autoCapitalization: z.string().trim()
|
||||
autoCapitalization: z.boolean()
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ export const INTEGRATION_TRAVISCI = "travisci";
|
||||
export const INTEGRATION_TEAMCITY = "teamcity";
|
||||
export const INTEGRATION_SUPABASE = "supabase";
|
||||
export const INTEGRATION_CHECKLY = "checkly";
|
||||
export const INTEGRATION_QOVERY = "qovery";
|
||||
export const INTEGRATION_TERRAFORM_CLOUD = "terraform-cloud";
|
||||
export const INTEGRATION_HASHICORP_VAULT = "hashicorp-vault";
|
||||
export const INTEGRATION_CLOUDFLARE_PAGES = "cloudflare-pages";
|
||||
@@ -53,6 +54,7 @@ export const INTEGRATION_SET = new Set([
|
||||
INTEGRATION_TEAMCITY,
|
||||
INTEGRATION_SUPABASE,
|
||||
INTEGRATION_CHECKLY,
|
||||
INTEGRATION_QOVERY,
|
||||
INTEGRATION_TERRAFORM_CLOUD,
|
||||
INTEGRATION_HASHICORP_VAULT,
|
||||
INTEGRATION_CLOUDFLARE_PAGES,
|
||||
@@ -83,6 +85,7 @@ export const INTEGRATION_BITBUCKET_TOKEN_URL = "https://bitbucket.org/site/oauth
|
||||
export const INTEGRATION_GCP_API_URL = "https://cloudresourcemanager.googleapis.com";
|
||||
export const INTEGRATION_HEROKU_API_URL = "https://api.heroku.com";
|
||||
export const INTEGRATION_GITLAB_API_URL = "https://gitlab.com/api";
|
||||
export const INTEGRATION_GITHUB_API_URL = "https://api.github.com";
|
||||
export const INTEGRATION_VERCEL_API_URL = "https://api.vercel.com";
|
||||
export const INTEGRATION_NETLIFY_API_URL = "https://api.netlify.com";
|
||||
export const INTEGRATION_RENDER_API_URL = "https://api.render.com";
|
||||
@@ -93,6 +96,7 @@ export const INTEGRATION_TRAVISCI_API_URL = "https://api.travis-ci.com";
|
||||
export const INTEGRATION_SUPABASE_API_URL = "https://api.supabase.com";
|
||||
export const INTEGRATION_LARAVELFORGE_API_URL = "https://forge.laravel.com";
|
||||
export const INTEGRATION_CHECKLY_API_URL = "https://api.checklyhq.com";
|
||||
export const INTEGRATION_QOVERY_API_URL = "https://api.qovery.com";
|
||||
export const INTEGRATION_TERRAFORM_CLOUD_API_URL = "https://app.terraform.io";
|
||||
export const INTEGRATION_CLOUDFLARE_PAGES_API_URL = "https://api.cloudflare.com";
|
||||
export const INTEGRATION_BITBUCKET_API_URL = "https://api.bitbucket.org";
|
||||
@@ -272,6 +276,15 @@ export const getIntegrationOptions = async () => {
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "Qovery",
|
||||
slug: "qovery",
|
||||
image: "Qovery.png",
|
||||
isAvailable: true,
|
||||
type: "pat",
|
||||
clientId: "",
|
||||
docsLink: "",
|
||||
},
|
||||
{
|
||||
name: "HashiCorp Vault",
|
||||
slug: "hashicorp-vault",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/Infisical/infisical-merge
|
||||
|
||||
go 1.19
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/charmbracelet/lipgloss v0.5.0
|
||||
|
||||
@@ -198,7 +198,7 @@ var secretsSetCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
// Key and value from argument
|
||||
key := strings.ToUpper(splitKeyValueFromArg[0])
|
||||
key := splitKeyValueFromArg[0]
|
||||
value := splitKeyValueFromArg[1]
|
||||
|
||||
hashedKey := fmt.Sprintf("%x", sha256.Sum256([]byte(key)))
|
||||
@@ -417,7 +417,7 @@ func getSecretsByNames(cmd *cobra.Command, args []string) {
|
||||
secretsMap := getSecretsByKeys(secrets)
|
||||
|
||||
for _, secretKeyFromArg := range args {
|
||||
if value, ok := secretsMap[strings.ToUpper(secretKeyFromArg)]; ok {
|
||||
if value, ok := secretsMap[secretKeyFromArg]; ok {
|
||||
requestedSecrets = append(requestedSecrets, value)
|
||||
} else {
|
||||
requestedSecrets = append(requestedSecrets, models.SingleEnvironmentVariable{
|
||||
@@ -625,7 +625,7 @@ func generateExampleEnv(cmd *cobra.Command, args []string) {
|
||||
func CenterString(s string, numStars int) string {
|
||||
stars := strings.Repeat("*", numStars)
|
||||
padding := (numStars - len(s)) / 2
|
||||
cenetredTextWithStar := stars[:padding] + " " + strings.ToUpper(s) + " " + stars[padding:]
|
||||
cenetredTextWithStar := stars[:padding] + " " + s + " " + stars[padding:]
|
||||
|
||||
hashes := strings.Repeat("#", len(cenetredTextWithStar)+2)
|
||||
return fmt.Sprintf("%s \n# %s \n%s", hashes, cenetredTextWithStar, hashes)
|
||||
|
||||
@@ -4,6 +4,12 @@ title: "Changelog"
|
||||
|
||||
The changelog below reflects new product developments and updates on a monthly basis.
|
||||
|
||||
## September
|
||||
|
||||
- Released an update to access controls; every user role now clearly defines and enforces a certain set of conditions across Infisical.
|
||||
- Updated UI/UX for integrations.
|
||||
- Added a native integration with [Qovery](https://infisical.com/docs/integrations/cloud/qovery).
|
||||
|
||||
## August 2023
|
||||
|
||||
- Release Audit Logs V2.
|
||||
|
||||
BIN
docs/images/integrations/qovery/integrations-qovery-auth.png
Normal file
BIN
docs/images/integrations/qovery/integrations-qovery-auth.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 464 KiB |
BIN
docs/images/integrations/qovery/integrations-qovery-create-1.png
Normal file
BIN
docs/images/integrations/qovery/integrations-qovery-create-1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.7 MiB |
BIN
docs/images/integrations/qovery/integrations-qovery-create-2.png
Normal file
BIN
docs/images/integrations/qovery/integrations-qovery-create-2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 MiB |
BIN
docs/images/integrations/qovery/integrations-qovery-token.png
Normal file
BIN
docs/images/integrations/qovery/integrations-qovery-token.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 971 KiB |
BIN
docs/images/integrations/qovery/integrations-qovery.png
Normal file
BIN
docs/images/integrations/qovery/integrations-qovery.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
43
docs/integrations/cloud/qovery.mdx
Normal file
43
docs/integrations/cloud/qovery.mdx
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
title: "Qovery"
|
||||
description: "How to sync secrets from Infisical to Qovery"
|
||||
---
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||
|
||||
## Navigate to your project's integrations tab
|
||||
|
||||

|
||||
|
||||
## Enter your Qovery API Token
|
||||
|
||||
Obtain a Qovery API Token in Settings > API Token.
|
||||
|
||||

|
||||
|
||||
Press on the Qovery tile and input your Qovery API Token to grant Infisical access to your Qovery account.
|
||||
|
||||

|
||||
|
||||
<Info>
|
||||
If this is your project's first cloud integration, then you'll have to grant
|
||||
Infisical access to your project's environment variables. Although this step
|
||||
breaks E2EE, it is necessary for Infisical to sync the environment variables to
|
||||
the cloud platform.
|
||||
</Info>
|
||||
|
||||
## Start integration
|
||||
|
||||
Select which Infisical environment secrets you want to sync to Qovery and press create integration to start syncing secrets.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
<Note>
|
||||
Infisical supports syncing secrets to various Qovery scopes including applications, jobs, or containers.
|
||||
</Note>
|
||||
|
||||

|
||||
34
docs/integrations/frameworks/bun.mdx
Normal file
34
docs/integrations/frameworks/bun.mdx
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
title: "Bun"
|
||||
description: "How to use Infisical to inject environment variables and secrets into a Bun app."
|
||||
---
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
||||
- [Install the CLI](/cli/overview)
|
||||
|
||||
## Initialize Infisical for your [Bun](https://bun.sh)
|
||||
|
||||
```bash
|
||||
# navigate to the root of your of your project
|
||||
cd /path/to/project
|
||||
|
||||
# then initialize infisical
|
||||
infisical init
|
||||
```
|
||||
|
||||
## Start your application as usual but with Infisical
|
||||
|
||||
```bash
|
||||
infisical run -- <your application start command>
|
||||
|
||||
# Example
|
||||
infisical run -- bun run dev
|
||||
```
|
||||
|
||||
<Info>
|
||||
Bun environment variables can be called as either `Bun.env.SECRET` or `process.env.SECRET`. We also recommend you check this more in-depth [guide to environment variables in Bun](https://infisical.com/blog/bun-environment-variables).
|
||||
</Info>
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/m1NNVuiP07M?si=wJtdQdhQ2ylGeRI8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
@@ -27,6 +27,7 @@ Missing an integration? [Throw in a request](https://github.com/Infisical/infisi
|
||||
| [Northflank](/integrations/cloud/northflank) | Cloud | Available |
|
||||
| [Cloudflare Pages](/integrations/cloud/cloudflare-pages) | Cloud | Available |
|
||||
| [Checkly](/integrations/cloud/checkly) | Cloud | Available |
|
||||
| [Qovery](/integrations/cloud/qovery) | Cloud | Available |
|
||||
| [HashiCorp Vault](/integrations/cloud/hashicorp-vault) | Cloud | Available |
|
||||
| [AWS Parameter Store](/integrations/cloud/aws-parameter-store) | Cloud | Available |
|
||||
| [AWS Secrets Manager](/integrations/cloud/aws-secret-manager) | Cloud | Available |
|
||||
|
||||
@@ -238,6 +238,7 @@
|
||||
"integrations/cloud/teamcity",
|
||||
"integrations/cloud/cloudflare-pages",
|
||||
"integrations/cloud/checkly",
|
||||
"integrations/cloud/qovery",
|
||||
"integrations/cloud/hashicorp-vault",
|
||||
"integrations/cloud/azure-key-vault",
|
||||
"integrations/cloud/gcp-secret-manager",
|
||||
|
||||
28
frontend/package-lock.json
generated
28
frontend/package-lock.json
generated
@@ -72,7 +72,6 @@
|
||||
"react": "^17.0.2",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-code-input": "^3.10.1",
|
||||
"react-contenteditable": "^3.3.7",
|
||||
"react-day-picker": "^8.8.0",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-grid-layout": "^1.3.4",
|
||||
@@ -13340,7 +13339,8 @@
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fast-diff": {
|
||||
"version": "1.3.0",
|
||||
@@ -19594,18 +19594,6 @@
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-contenteditable": {
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/react-contenteditable/-/react-contenteditable-3.3.7.tgz",
|
||||
"integrity": "sha512-GA9NbC0DkDdpN3iGvib/OMHWTJzDX2cfkgy5Tt98JJAbA3kLnyrNbBIpsSpPpq7T8d3scD39DHP+j8mAM7BIfQ==",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"prop-types": "^15.7.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.3"
|
||||
}
|
||||
},
|
||||
"node_modules/react-day-picker": {
|
||||
"version": "8.8.0",
|
||||
"resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.8.0.tgz",
|
||||
@@ -33272,7 +33260,8 @@
|
||||
"fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"dev": true
|
||||
},
|
||||
"fast-diff": {
|
||||
"version": "1.3.0",
|
||||
@@ -37807,15 +37796,6 @@
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"react-contenteditable": {
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/react-contenteditable/-/react-contenteditable-3.3.7.tgz",
|
||||
"integrity": "sha512-GA9NbC0DkDdpN3iGvib/OMHWTJzDX2cfkgy5Tt98JJAbA3kLnyrNbBIpsSpPpq7T8d3scD39DHP+j8mAM7BIfQ==",
|
||||
"requires": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"prop-types": "^15.7.1"
|
||||
}
|
||||
},
|
||||
"react-day-picker": {
|
||||
"version": "8.8.0",
|
||||
"resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.8.0.tgz",
|
||||
|
||||
@@ -80,7 +80,6 @@
|
||||
"react": "^17.0.2",
|
||||
"react-beautiful-dnd": "^13.1.1",
|
||||
"react-code-input": "^3.10.1",
|
||||
"react-contenteditable": "^3.3.7",
|
||||
"react-day-picker": "^8.8.0",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-grid-layout": "^1.3.4",
|
||||
|
||||
@@ -19,6 +19,7 @@ const integrationSlugNameMapping: Mapping = {
|
||||
"travisci": "TravisCI",
|
||||
"supabase": "Supabase",
|
||||
"checkly": "Checkly",
|
||||
"qovery": "Qovery",
|
||||
"terraform-cloud": "Terraform Cloud",
|
||||
"teamcity": "TeamCity",
|
||||
"hashicorp-vault": "Vault",
|
||||
|
||||
BIN
frontend/public/images/integrations/Qovery.png
Normal file
BIN
frontend/public/images/integrations/Qovery.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.1 KiB |
@@ -1,6 +1,5 @@
|
||||
/* eslint-disable react/no-danger */
|
||||
import { forwardRef, HTMLAttributes } from "react";
|
||||
import ContentEditable from "react-contenteditable";
|
||||
import sanitizeHtml, { DisallowedTagsModes } from "sanitize-html";
|
||||
|
||||
import { useToggle } from "@app/hooks";
|
||||
@@ -35,58 +34,52 @@ const syntaxHighlight = (content?: string | null, isVisible?: boolean) => {
|
||||
`<span class="ph-no-capture text-yellow">${<span class="ph-no-capture text-yello-200/80">${b}</span>}</span>`
|
||||
);
|
||||
|
||||
return newContent;
|
||||
// akhilmhdh: Dont remove this br. I am still clueless how this works but weirdly enough
|
||||
// when break is added a line break works properly
|
||||
return `${newContent}<br/>`;
|
||||
};
|
||||
|
||||
type Props = Omit<HTMLAttributes<HTMLDivElement>, "onChange" | "onBlur"> & {
|
||||
type Props = HTMLAttributes<HTMLTextAreaElement> & {
|
||||
value?: string | null;
|
||||
isVisible?: boolean;
|
||||
isDisabled?: boolean;
|
||||
onChange?: (val: string) => void;
|
||||
onBlur?: () => void;
|
||||
};
|
||||
|
||||
export const SecretInput = forwardRef<HTMLDivElement, Props>(
|
||||
({ value, isVisible, onChange, onBlur, isDisabled, ...props }, ref) => {
|
||||
const commonClassName = "font-mono text-sm caret-white border-none outline-none w-full break-all";
|
||||
|
||||
export const SecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
||||
({ value, isVisible, onBlur, isDisabled, onFocus, ...props }, ref) => {
|
||||
const [isSecretFocused, setIsSecretFocused] = useToggle();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="thin-scrollbar relative overflow-y-auto overflow-x-hidden"
|
||||
style={{ maxHeight: `${21 * 7}px` }}
|
||||
>
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: syntaxHighlight(value, isVisible || isSecretFocused)
|
||||
}}
|
||||
className={`absolute top-0 left-0 z-0 h-full w-full inline-block text-ellipsis whitespace-pre-wrap break-all ${
|
||||
!value && value !== "" && "italic text-red-600/70"
|
||||
}`}
|
||||
ref={ref}
|
||||
/>
|
||||
<ContentEditable
|
||||
className="relative z-10 h-full w-full text-ellipsis inline-block whitespace-pre-wrap break-all text-transparent caret-white outline-none"
|
||||
role="textbox"
|
||||
onChange={(evt) => {
|
||||
if (onChange) onChange(evt.currentTarget.innerText.trim());
|
||||
}}
|
||||
onFocus={() => setIsSecretFocused.on()}
|
||||
disabled={isDisabled}
|
||||
spellCheck={false}
|
||||
onBlur={() => {
|
||||
if (onBlur) onBlur();
|
||||
setIsSecretFocused.off();
|
||||
}}
|
||||
html={
|
||||
isVisible || isSecretFocused
|
||||
? sanitizeHtml(
|
||||
value?.replaceAll("<", "<").replaceAll(">", ">") || "",
|
||||
sanitizeConf
|
||||
)
|
||||
: syntaxHighlight(value, false)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
<div className="overflow-auto w-full" style={{ maxHeight: `${21 * 7}px` }}>
|
||||
<div className="relative overflow-hidden">
|
||||
<pre aria-hidden className="m-0 ">
|
||||
<code className={`inline-block w-full ${commonClassName}`}>
|
||||
<span
|
||||
style={{ whiteSpace: "break-spaces" }}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: syntaxHighlight(value, isVisible || isSecretFocused) ?? ""
|
||||
}}
|
||||
/>
|
||||
</code>
|
||||
</pre>
|
||||
<textarea
|
||||
style={{ whiteSpace: "break-spaces" }}
|
||||
aria-label="secret value"
|
||||
ref={ref}
|
||||
className={`absolute inset-0 block h-full resize-none overflow-hidden bg-transparent text-transparent no-scrollbar focus:border-0 ${commonClassName}`}
|
||||
onFocus={() => setIsSecretFocused.on()}
|
||||
disabled={isDisabled}
|
||||
spellCheck={false}
|
||||
onBlur={(evt) => {
|
||||
onBlur?.(evt);
|
||||
setIsSecretFocused.off();
|
||||
}}
|
||||
value={value || ""}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
Environment,
|
||||
IntegrationAuth,
|
||||
NorthflankSecretGroup,
|
||||
Org,
|
||||
Project,
|
||||
Service,
|
||||
Team,
|
||||
TeamCityBuildConfig} from "./types";
|
||||
@@ -27,6 +29,31 @@ const integrationAuthKeys = {
|
||||
integrationAuthId: string;
|
||||
appId: string;
|
||||
}) => [{ integrationAuthId, appId }, "integrationAuthVercelBranches"] as const,
|
||||
getIntegrationAuthQoveryOrgs: (integrationAuthId: string) =>
|
||||
[{ integrationAuthId }, "integrationAuthQoveryOrgs"] as const,
|
||||
getIntegrationAuthQoveryProjects: ({
|
||||
integrationAuthId,
|
||||
orgId
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
orgId: string;
|
||||
}) => [{ integrationAuthId, orgId }, "integrationAuthQoveryProjects"] as const,
|
||||
getIntegrationAuthQoveryEnvironments: ({
|
||||
integrationAuthId,
|
||||
projectId
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
projectId: string;
|
||||
}) => [{ integrationAuthId, projectId }, "integrationAuthQoveryEnvironments"] as const,
|
||||
getIntegrationAuthQoveryScopes: ({
|
||||
integrationAuthId,
|
||||
environmentId,
|
||||
scope
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
environmentId: string;
|
||||
scope: "job" | "application" | "container";
|
||||
}) => [{ integrationAuthId, environmentId, scope }, "integrationAuthQoveryScopes"] as const,
|
||||
getIntegrationAuthRailwayEnvironments: ({
|
||||
integrationAuthId,
|
||||
appId
|
||||
@@ -120,6 +147,121 @@ const fetchIntegrationAuthVercelBranches = async ({
|
||||
return branches;
|
||||
};
|
||||
|
||||
const fetchIntegrationAuthQoveryOrgs = async (integrationAuthId: string) => {
|
||||
const {
|
||||
data: { orgs }
|
||||
} = await apiRequest.get<{ orgs: Org[] }>(
|
||||
`/api/v1/integration-auth/${integrationAuthId}/qovery/orgs`
|
||||
);
|
||||
|
||||
return orgs;
|
||||
};
|
||||
|
||||
const fetchIntegrationAuthQoveryProjects = async ({
|
||||
integrationAuthId,
|
||||
orgId
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
orgId: string;
|
||||
}) => {
|
||||
if (orgId === "none") return [];
|
||||
|
||||
const {
|
||||
data: { projects }
|
||||
} = await apiRequest.get<{ projects: Project[] }>(
|
||||
`/api/v1/integration-auth/${integrationAuthId}/qovery/projects`,
|
||||
{
|
||||
params: {
|
||||
orgId
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return projects;
|
||||
};
|
||||
|
||||
const fetchIntegrationAuthQoveryEnvironments = async ({
|
||||
integrationAuthId,
|
||||
projectId
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
projectId: string;
|
||||
}) => {
|
||||
if (projectId === "none") return [];
|
||||
|
||||
const {
|
||||
data: { environments }
|
||||
} = await apiRequest.get<{ environments: Environment[] }>(
|
||||
`/api/v1/integration-auth/${integrationAuthId}/qovery/environments`,
|
||||
{
|
||||
params: {
|
||||
projectId
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return environments;
|
||||
};
|
||||
|
||||
const fetchIntegrationAuthQoveryScopes = async ({
|
||||
integrationAuthId,
|
||||
environmentId,
|
||||
scope
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
environmentId: string;
|
||||
scope: "job" | "application" | "container";
|
||||
}) => {
|
||||
if (environmentId === "none") return [];
|
||||
|
||||
if (scope === "application") {
|
||||
const {
|
||||
data: { apps }
|
||||
} = await apiRequest.get<{ apps: App[] }>(
|
||||
`/api/v1/integration-auth/${integrationAuthId}/qovery/apps`,
|
||||
{
|
||||
params: {
|
||||
environmentId
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return apps;
|
||||
}
|
||||
|
||||
if (scope === "container") {
|
||||
const {
|
||||
data: { containers }
|
||||
} = await apiRequest.get<{ containers: App[] }>(
|
||||
`/api/v1/integration-auth/${integrationAuthId}/qovery/containers`,
|
||||
{
|
||||
params: {
|
||||
environmentId
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return containers;
|
||||
}
|
||||
|
||||
if (scope === "job") {
|
||||
const {
|
||||
data: { jobs }
|
||||
} = await apiRequest.get<{ jobs: App[] }>(
|
||||
`/api/v1/integration-auth/${integrationAuthId}/qovery/jobs`,
|
||||
{
|
||||
params: {
|
||||
environmentId
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return jobs;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const fetchIntegrationAuthRailwayEnvironments = async ({
|
||||
integrationAuthId,
|
||||
appId
|
||||
@@ -197,6 +339,8 @@ const fetchIntegrationAuthTeamCityBuildConfigs = async ({
|
||||
integrationAuthId: string;
|
||||
appId: string;
|
||||
}) => {
|
||||
if (appId === "") return [];
|
||||
|
||||
const {
|
||||
data: { buildConfigs }
|
||||
} = await apiRequest.get<{ buildConfigs: TeamCityBuildConfig[] }>(
|
||||
@@ -269,6 +413,82 @@ export const useGetIntegrationAuthVercelBranches = ({
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetIntegrationAuthQoveryOrgs = (integrationAuthId: string) => {
|
||||
return useQuery({
|
||||
queryKey: integrationAuthKeys.getIntegrationAuthQoveryOrgs(integrationAuthId),
|
||||
queryFn: () =>
|
||||
fetchIntegrationAuthQoveryOrgs(integrationAuthId),
|
||||
enabled: true
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetIntegrationAuthQoveryProjects = ({
|
||||
integrationAuthId,
|
||||
orgId
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
orgId: string;
|
||||
}) => {
|
||||
return useQuery({
|
||||
queryKey: integrationAuthKeys.getIntegrationAuthQoveryProjects({
|
||||
integrationAuthId,
|
||||
orgId
|
||||
}),
|
||||
queryFn: () =>
|
||||
fetchIntegrationAuthQoveryProjects({
|
||||
integrationAuthId,
|
||||
orgId
|
||||
}),
|
||||
enabled: true
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetIntegrationAuthQoveryEnvironments = ({
|
||||
integrationAuthId,
|
||||
projectId
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
projectId: string;
|
||||
}) => {
|
||||
return useQuery({
|
||||
queryKey: integrationAuthKeys.getIntegrationAuthQoveryEnvironments({
|
||||
integrationAuthId,
|
||||
projectId
|
||||
}),
|
||||
queryFn: () =>
|
||||
fetchIntegrationAuthQoveryEnvironments({
|
||||
integrationAuthId,
|
||||
projectId
|
||||
}),
|
||||
enabled: true
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetIntegrationAuthQoveryScopes = ({
|
||||
integrationAuthId,
|
||||
environmentId,
|
||||
scope
|
||||
}: {
|
||||
integrationAuthId: string;
|
||||
environmentId: string;
|
||||
scope: "job" | "application" | "container";
|
||||
}) => {
|
||||
return useQuery({
|
||||
queryKey: integrationAuthKeys.getIntegrationAuthQoveryScopes({
|
||||
integrationAuthId,
|
||||
environmentId,
|
||||
scope
|
||||
}),
|
||||
queryFn: () =>
|
||||
fetchIntegrationAuthQoveryScopes({
|
||||
integrationAuthId,
|
||||
environmentId,
|
||||
scope
|
||||
}),
|
||||
enabled: true
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetIntegrationAuthRailwayEnvironments = ({
|
||||
integrationAuthId,
|
||||
appId
|
||||
|
||||
@@ -26,6 +26,21 @@ export type Environment = {
|
||||
environmentId: string;
|
||||
};
|
||||
|
||||
export type Container = {
|
||||
name: string;
|
||||
containerId: string;
|
||||
};
|
||||
|
||||
export type Org = {
|
||||
name: string;
|
||||
orgId: string;
|
||||
};
|
||||
|
||||
export type Project = {
|
||||
name: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export type Service = {
|
||||
name: string;
|
||||
serviceId: string;
|
||||
|
||||
@@ -40,6 +40,7 @@ export const useCreateIntegration = () => {
|
||||
owner,
|
||||
path,
|
||||
region,
|
||||
scope,
|
||||
secretPath,
|
||||
metadata
|
||||
}: {
|
||||
@@ -56,6 +57,7 @@ export const useCreateIntegration = () => {
|
||||
owner?: string;
|
||||
path?: string;
|
||||
region?: string;
|
||||
scope?: string;
|
||||
metadata?: {
|
||||
secretPrefix?: string;
|
||||
secretSuffix?: string;
|
||||
@@ -73,6 +75,7 @@ export const useCreateIntegration = () => {
|
||||
targetServiceId,
|
||||
owner,
|
||||
path,
|
||||
scope,
|
||||
region,
|
||||
secretPath,
|
||||
metadata
|
||||
|
||||
@@ -32,5 +32,9 @@ export type TIntegration = {
|
||||
__v: number;
|
||||
metadata?: {
|
||||
secretSuffix?: string;
|
||||
scope: string;
|
||||
org: string;
|
||||
project: string;
|
||||
environment: string;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -487,7 +487,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
{/* <Link href={`/project/${currentWorkspace?._id}/audit-logs`} passHref>
|
||||
<Link href={`/project/${currentWorkspace?._id}/audit-logs`} passHref>
|
||||
<a>
|
||||
<MenuItem
|
||||
isSelected={
|
||||
@@ -498,7 +498,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
Audit Logs
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link> */}
|
||||
</Link>
|
||||
<Link href={`/project/${currentWorkspace?._id}/settings`} passHref>
|
||||
<a>
|
||||
<MenuItem
|
||||
|
||||
@@ -84,6 +84,7 @@ export default function GitLabCreateIntegrationPage() {
|
||||
const selectedSourceEnvironment = watch("selectedSourceEnvironment");
|
||||
const targetEntity = watch("targetEntity");
|
||||
const targetTeamId = watch("targetTeamId");
|
||||
const targetAppIdValue = watch("targetAppId");
|
||||
|
||||
const { mutateAsync } = useCreateIntegration();
|
||||
|
||||
@@ -440,6 +441,7 @@ export default function GitLabCreateIntegrationPage() {
|
||||
size="sm"
|
||||
type="submit"
|
||||
isLoading={isLoading}
|
||||
isDisabled={targetAppIdValue === "none"}
|
||||
>
|
||||
Create Integration
|
||||
</Button>
|
||||
|
||||
106
frontend/src/pages/integrations/qovery/authorize.tsx
Normal file
106
frontend/src/pages/integrations/qovery/authorize.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import { useState } from "react";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import {
|
||||
useSaveIntegrationAccessToken
|
||||
} from "@app/hooks/api";
|
||||
|
||||
import { Button, Card, CardTitle, FormControl, Input } from "../../../components/v2";
|
||||
|
||||
export default function QoveryCreateIntegrationPage() {
|
||||
const router = useRouter();
|
||||
const { mutateAsync } = useSaveIntegrationAccessToken();
|
||||
|
||||
const [accessToken, setAccessToken] = useState("");
|
||||
const [accessTokenErrorText, setAccessTokenErrorText] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleButtonClick = async () => {
|
||||
try {
|
||||
setAccessTokenErrorText("");
|
||||
if (accessToken.length === 0) {
|
||||
setAccessTokenErrorText("Access token cannot be blank");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
const integrationAuth = await mutateAsync({
|
||||
workspaceId: localStorage.getItem("projectData.id"),
|
||||
integration: "qovery",
|
||||
accessToken
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
router.push(`/integrations/qovery/create?integrationAuthId=${integrationAuth._id}`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Head>
|
||||
<title>Authorize Qovery Integration</title>
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
<Card className="max-w-lg rounded-md border border-mineshaft-600 mb-12">
|
||||
<CardTitle
|
||||
className="text-left px-6 text-xl"
|
||||
subTitle="After adding your API key, you will be prompted to set up an integration for a particular Infisical project and environment."
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="inline flex items-center pb-0.5">
|
||||
<Image
|
||||
src="/images/integrations/Qovery.png"
|
||||
height={30}
|
||||
width={30}
|
||||
alt="Qovery logo"
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-2.5">Qovery Integration </span>
|
||||
<Link href="https://infisical.com/docs/integrations/cloud/qovery" passHref>
|
||||
<a target="_blank" rel="noopener noreferrer">
|
||||
<div className="ml-2 mb-1 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
||||
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5"/>
|
||||
Docs
|
||||
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="ml-1.5 text-xxs mb-[0.07rem]"/>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</CardTitle>
|
||||
<FormControl
|
||||
label="Qovery API token"
|
||||
errorText={accessTokenErrorText}
|
||||
isError={accessTokenErrorText !== "" ?? false}
|
||||
className="mx-6"
|
||||
>
|
||||
<Input
|
||||
placeholder=""
|
||||
value={accessToken}
|
||||
onChange={(e) => setAccessToken(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
className="mb-6 mt-2 ml-auto mr-6 w-min"
|
||||
isFullWidth={false}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Connect to Qovery
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
QoveryCreateIntegrationPage.requireAuth = true;
|
||||
409
frontend/src/pages/integrations/qovery/create.tsx
Normal file
409
frontend/src/pages/integrations/qovery/create.tsx
Normal file
@@ -0,0 +1,409 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { faArrowUpRightFromSquare, faBookOpen, faBugs } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { motion } from "framer-motion";
|
||||
import queryString from "query-string";
|
||||
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardTitle,
|
||||
FormControl,
|
||||
Input,
|
||||
Select,
|
||||
SelectItem,
|
||||
Tab,
|
||||
TabList,
|
||||
TabPanel,
|
||||
Tabs
|
||||
} from "@app/components/v2";
|
||||
import {
|
||||
useCreateIntegration
|
||||
} from "@app/hooks/api";
|
||||
import {
|
||||
useGetIntegrationAuthQoveryEnvironments,
|
||||
useGetIntegrationAuthQoveryOrgs,
|
||||
useGetIntegrationAuthQoveryProjects,
|
||||
useGetIntegrationAuthQoveryScopes
|
||||
} from "@app/hooks/api/integrationAuth/queries";
|
||||
|
||||
import { useGetIntegrationAuthById } from "../../../hooks/api/integrationAuth";
|
||||
import { useGetWorkspaceById } from "../../../hooks/api/workspace";
|
||||
|
||||
const qoveryScopes = [
|
||||
{ label: "Application", value: "application" },
|
||||
{ label: "Container", value: "container" },
|
||||
{ label: "Job", value: "job" }
|
||||
];
|
||||
|
||||
enum TabSections {
|
||||
InfisicalSettings = "infisicalSettings",
|
||||
QoverySettings = "qoverySettings"
|
||||
}
|
||||
|
||||
/**
|
||||
* Follow the logic:
|
||||
* - Access token
|
||||
* -> Get organizations (orgId) - select an organization
|
||||
* -> Get projects belonging to the organization - select project
|
||||
* -> Get environments belonging to the project - select environmnet
|
||||
* -> Get qovery scopes / application (this is the application to sync to)
|
||||
*
|
||||
* Do we even need to get organizations?
|
||||
*/
|
||||
|
||||
export default function QoveryCreateIntegrationPage() {
|
||||
const router = useRouter();
|
||||
const { mutateAsync } = useCreateIntegration();
|
||||
|
||||
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
||||
|
||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||
const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? "");
|
||||
|
||||
const [scope, setScope] = useState("application");
|
||||
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
|
||||
const [secretPath, setSecretPath] = useState("/");
|
||||
|
||||
const { data: integrationAuthOrgs } = useGetIntegrationAuthQoveryOrgs((integrationAuthId as string) ?? "");
|
||||
const [targetOrgId, setTargetOrgId] = useState("");
|
||||
|
||||
const { data: integrationAuthProjects } = useGetIntegrationAuthQoveryProjects({
|
||||
integrationAuthId: (integrationAuthId as string) ?? "",
|
||||
orgId: targetOrgId
|
||||
});
|
||||
const [targetProjectId, setTargetProjectId] = useState("");
|
||||
|
||||
const { data: integrationAuthEnvironments } = useGetIntegrationAuthQoveryEnvironments({
|
||||
integrationAuthId: (integrationAuthId as string) ?? "",
|
||||
projectId: targetProjectId
|
||||
});
|
||||
const [targetEnvironmentId, setTargetEnvironmentId] = useState("");
|
||||
|
||||
const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } = useGetIntegrationAuthQoveryScopes({
|
||||
integrationAuthId: (integrationAuthId as string) ?? "",
|
||||
environmentId: targetEnvironmentId,
|
||||
scope: (scope as ("job" | "application" | "container"))
|
||||
});
|
||||
const [targetAppId, setTargetAppId] = useState("");
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (workspace) {
|
||||
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
||||
}
|
||||
}, [workspace]);
|
||||
|
||||
useEffect(() => {
|
||||
if (integrationAuthApps) {
|
||||
if (integrationAuthApps.length > 0) {
|
||||
setTargetAppId(String(integrationAuthApps[0].appId));
|
||||
} else {
|
||||
setTargetAppId("none");
|
||||
}
|
||||
}
|
||||
}, [integrationAuthApps]);
|
||||
|
||||
useEffect(() => {
|
||||
if (integrationAuthOrgs) {
|
||||
if (integrationAuthOrgs.length > 0) {
|
||||
setTargetOrgId(String(integrationAuthOrgs[0].orgId));
|
||||
} else {
|
||||
setTargetOrgId("none");
|
||||
}
|
||||
}
|
||||
}, [integrationAuthOrgs]);
|
||||
|
||||
useEffect(() => {
|
||||
if (integrationAuthProjects) {
|
||||
if (integrationAuthProjects.length > 0) {
|
||||
setTargetProjectId(String(integrationAuthProjects[0].projectId));
|
||||
} else {
|
||||
setTargetProjectId("none");
|
||||
}
|
||||
}
|
||||
}, [integrationAuthProjects]);
|
||||
|
||||
useEffect(() => {
|
||||
if (integrationAuthEnvironments) {
|
||||
if (integrationAuthEnvironments.length > 0) {
|
||||
setTargetEnvironmentId(String(integrationAuthEnvironments[0].environmentId));
|
||||
} else {
|
||||
setTargetEnvironmentId("none");
|
||||
}
|
||||
}
|
||||
}, [integrationAuthEnvironments]);
|
||||
|
||||
const handleButtonClick = async () => {
|
||||
try {
|
||||
if (!integrationAuth?._id) return;
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
const targetOrg = integrationAuthOrgs?.find((integrationAuthOrg) => integrationAuthOrg.orgId === targetOrgId)?.name;
|
||||
const targetProject = integrationAuthProjects?.find((integrationAuthProject) => integrationAuthProject.projectId === targetProjectId)?.name;
|
||||
const targetEnvironment = integrationAuthEnvironments?.find((integrationAuthEnvironment) => integrationAuthEnvironment.environmentId === targetEnvironmentId)?.name;
|
||||
const targetApp = integrationAuthApps?.find((integrationAuthApp) => integrationAuthApp.appId === targetAppId)?.name;
|
||||
|
||||
await mutateAsync({
|
||||
integrationAuthId: integrationAuth?._id,
|
||||
isActive: true,
|
||||
app: targetApp,
|
||||
appId: targetAppId,
|
||||
scope,
|
||||
sourceEnvironment: selectedSourceEnvironment,
|
||||
targetEnvironment,
|
||||
targetEnvironmentId,
|
||||
targetService: targetProject,
|
||||
targetServiceId: targetProjectId,
|
||||
owner: targetOrg,
|
||||
secretPath
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
router.push(`/integrations/${localStorage.getItem("projectData.id")}`);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
return integrationAuth &&
|
||||
workspace &&
|
||||
selectedSourceEnvironment ? (
|
||||
<div className="flex flex-col h-full w-full items-center justify-center bg-gradient-to-tr from-mineshaft-900 to-bunker-900">
|
||||
<Head>
|
||||
<title>Set Up Qovery Integration</title>
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
<Card className="max-w-lg rounded-md border border-mineshaft-600 p-0">
|
||||
<CardTitle
|
||||
className="text-left px-6 text-xl"
|
||||
subTitle="Choose which environment in Infisical you want to sync to Checkly environment variables."
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="inline flex items-center pb-0.5">
|
||||
<Image
|
||||
src="/images/integrations/Qovery.png"
|
||||
height={30}
|
||||
width={30}
|
||||
alt="Qovery logo"
|
||||
/>
|
||||
</div>
|
||||
<span className="ml-2.5">Qovery Integration </span>
|
||||
<Link href="https://infisical.com/docs/integrations/cloud/qovery" passHref>
|
||||
<a target="_blank" rel="noopener noreferrer">
|
||||
<div className="ml-2 mb-1 rounded-md text-yellow text-sm inline-block bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] opacity-80 hover:opacity-100 cursor-default">
|
||||
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5"/>
|
||||
Docs
|
||||
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="ml-1.5 text-xxs mb-[0.07rem]"/>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</CardTitle>
|
||||
<Tabs defaultValue={TabSections.InfisicalSettings} className="px-6">
|
||||
<TabList>
|
||||
<div className="flex flex-row border-b border-mineshaft-600 w-full">
|
||||
<Tab value={TabSections.InfisicalSettings}>Infisical Settings</Tab>
|
||||
<Tab value={TabSections.QoverySettings}>Qovery Settings</Tab>
|
||||
</div>
|
||||
</TabList>
|
||||
<TabPanel value={TabSections.InfisicalSettings}>
|
||||
<motion.div
|
||||
key="panel-1"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<FormControl label="Infisical Project Environment">
|
||||
<Select
|
||||
value={selectedSourceEnvironment}
|
||||
onValueChange={(val) => setSelectedSourceEnvironment(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
>
|
||||
{workspace?.environments.map((sourceEnvironment) => (
|
||||
<SelectItem
|
||||
value={sourceEnvironment.slug}
|
||||
key={`source-environment-${sourceEnvironment.slug}`}
|
||||
>
|
||||
{sourceEnvironment.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl label="Secrets Path" className="pb-[14.68rem]">
|
||||
<Input
|
||||
value={secretPath}
|
||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||
placeholder="Provide a path, default is /"
|
||||
/>
|
||||
</FormControl>
|
||||
</motion.div>
|
||||
</TabPanel>
|
||||
<TabPanel value={TabSections.QoverySettings}>
|
||||
<motion.div
|
||||
key="panel-1"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: -30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<FormControl label="Qovery Scope">
|
||||
<Select
|
||||
value={scope}
|
||||
onValueChange={(val) => setScope(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
>
|
||||
{qoveryScopes.map((qoveryScope) => (
|
||||
<SelectItem
|
||||
value={qoveryScope.value}
|
||||
key={`target-app-${qoveryScope.value}`}
|
||||
>
|
||||
{qoveryScope.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
{integrationAuthOrgs && <FormControl label="Qovery Organization">
|
||||
<Select
|
||||
value={targetOrgId}
|
||||
onValueChange={(val) => setTargetOrgId(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
isDisabled={integrationAuthOrgs.length === 0}
|
||||
>
|
||||
{integrationAuthOrgs.length > 0 ? (
|
||||
integrationAuthOrgs.map((integrationAuthOrg) => (
|
||||
<SelectItem
|
||||
value={integrationAuthOrg.orgId}
|
||||
key={`target-app-${integrationAuthOrg.orgId}`}
|
||||
>
|
||||
{integrationAuthOrg.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-app-none">
|
||||
No organizaztions found
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>}
|
||||
{integrationAuthProjects && <FormControl label="Qovery Project">
|
||||
<Select
|
||||
value={targetProjectId}
|
||||
onValueChange={(val) => setTargetProjectId(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
isDisabled={integrationAuthProjects.length === 0}
|
||||
>
|
||||
{integrationAuthProjects.length > 0 ? (
|
||||
integrationAuthProjects.map((integrationAuthProject) => (
|
||||
<SelectItem
|
||||
value={integrationAuthProject.projectId}
|
||||
key={`target-app-${integrationAuthProject.projectId}`}
|
||||
>
|
||||
{integrationAuthProject.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-app-none">
|
||||
No projects found
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>}
|
||||
{integrationAuthEnvironments && <FormControl label="Qovery Environment">
|
||||
<Select
|
||||
value={targetEnvironmentId}
|
||||
onValueChange={(val) => setTargetEnvironmentId(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
isDisabled={integrationAuthEnvironments.length === 0}
|
||||
>
|
||||
{integrationAuthEnvironments.length > 0 ? (
|
||||
integrationAuthEnvironments.map((integrationAuthEnvironment) => (
|
||||
<SelectItem
|
||||
value={integrationAuthEnvironment.environmentId}
|
||||
key={`target-app-${integrationAuthEnvironment.environmentId}`}
|
||||
>
|
||||
{integrationAuthEnvironment.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-app-none">
|
||||
No environments found
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>}
|
||||
{(scope && integrationAuthApps) && <FormControl label={`Qovery ${scope.charAt(0).toUpperCase() + scope.slice(1)}`}>
|
||||
<Select
|
||||
value={targetAppId}
|
||||
onValueChange={(val) => setTargetAppId(val)}
|
||||
className="w-full border border-mineshaft-500"
|
||||
isDisabled={integrationAuthApps.length === 0}
|
||||
>
|
||||
{integrationAuthApps.length > 0 ? (
|
||||
integrationAuthApps.map((integrationAuthApp) => (
|
||||
<SelectItem
|
||||
value={(integrationAuthApp.appId as string)}
|
||||
key={`target-app-${integrationAuthApp.appId}`}
|
||||
>
|
||||
{integrationAuthApp.name}
|
||||
</SelectItem>
|
||||
))
|
||||
) : (
|
||||
<SelectItem value="none" key="target-app-none">
|
||||
No {scope}s found
|
||||
</SelectItem>
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>}
|
||||
</motion.div>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
variant="outline_bg"
|
||||
className="mb-6 ml-auto mr-6"
|
||||
isFullWidth={false}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Create Integration
|
||||
</Button>
|
||||
</Card>
|
||||
{/* <div className="border-t border-mineshaft-800 w-full max-w-md mt-6"/>
|
||||
<div className="flex flex-col bg-mineshaft-800 border border-mineshaft-600 w-full p-4 max-w-lg mt-6 rounded-md">
|
||||
<div className="flex flex-row items-center"><FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-200 text-xl"/> <span className="ml-3 text-md text-mineshaft-100">Pro Tips</span></div>
|
||||
<span className="text-mineshaft-300 text-sm mt-4">After creating an integration, your secrets will start syncing immediately. This might cause an unexpected override of current secrets in Qovery with secrets from Infisical.</span>
|
||||
</div> */}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-center items-center w-full h-full">
|
||||
<Head>
|
||||
<title>Set Up Qovery Integration</title>
|
||||
<link rel='icon' href='/infisical.ico' />
|
||||
</Head>
|
||||
{isIntegrationAuthAppsLoading ? <img src="/images/loading/loading.gif" height={70} width={120} alt="infisical loading indicator" /> : <div className="max-w-md h-max p-6 border border-mineshaft-600 rounded-md bg-mineshaft-800 text-mineshaft-200 flex flex-col text-center">
|
||||
<FontAwesomeIcon icon={faBugs} className="text-6xl my-2 inlineli"/>
|
||||
<p>
|
||||
Something went wrong. Please contact <a
|
||||
className="inline underline underline-offset-4 decoration-primary-500 opacity-80 hover:opacity-100 text-mineshaft-100 duration-200 cursor-pointer"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="mailto:support@infisical.com"
|
||||
>
|
||||
support@infisical.com
|
||||
</a> if the issue persists.
|
||||
</p>
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
QoveryCreateIntegrationPage.requireAuth = true;
|
||||
@@ -49,7 +49,7 @@ export default function TeamCityCreateIntegrationPage() {
|
||||
integrationAuthId: (integrationAuthId as string) ?? "",
|
||||
appId: targetAppId
|
||||
});
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (workspace) {
|
||||
setSelectedSourceEnvironment(workspace.environments[0].slug);
|
||||
|
||||
@@ -270,7 +270,7 @@ export const SecretInputRow = memo(
|
||||
className="flex w-full flex-grow flex-row border-r border-none border-red"
|
||||
style={{ padding: "0.5rem 0 0.5rem 1rem" }}
|
||||
>
|
||||
<div className="w-full">
|
||||
<div className="w-full flex items-center">
|
||||
{isOverridden ? (
|
||||
<Controller
|
||||
control={control}
|
||||
|
||||
@@ -87,6 +87,9 @@ export const redirectForProviderAuth = (integrationOption: TCloudIntegration) =>
|
||||
case "checkly":
|
||||
link = `${window.location.origin}/integrations/checkly/authorize`;
|
||||
break;
|
||||
case "qovery":
|
||||
link = `${window.location.origin}/integrations/qovery/authorize`;
|
||||
break;
|
||||
case "railway":
|
||||
link = `${window.location.origin}/integrations/railway/authorize`;
|
||||
break;
|
||||
|
||||
@@ -96,8 +96,30 @@ export const IntegrationsSection = ({
|
||||
{integrationSlugNameMapping[integration.integration]}
|
||||
</div>
|
||||
</div>
|
||||
{(integration.integration === "qovery") && (
|
||||
<div className="flex flex-row">
|
||||
<div className="ml-2 flex flex-col">
|
||||
<FormLabel label="Org" />
|
||||
<div className="rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">
|
||||
{integration?.owner || "-"}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-2 flex flex-col">
|
||||
<FormLabel label="Project" />
|
||||
<div className="rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">
|
||||
{integration?.targetService || "-"}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-2 flex flex-col">
|
||||
<FormLabel label="Env" />
|
||||
<div className="rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">
|
||||
{integration?.targetEnvironment || "-"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="ml-2 flex flex-col">
|
||||
<FormLabel label="App" />
|
||||
<FormLabel label={integration?.metadata?.scope || "App"} />
|
||||
<div className="min-w-[8rem] rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">
|
||||
{integration.integration === "hashicorp-vault"
|
||||
? `${integration.app} - path: ${integration.path}`
|
||||
@@ -131,7 +153,7 @@ export const IntegrationsSection = ({
|
||||
I={ProjectPermissionActions.Delete}
|
||||
a={ProjectPermissionSub.Integrations}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
{(isAllowed: boolean) => (
|
||||
<div className="ml-2 opacity-80 duration-200 hover:opacity-100">
|
||||
<Tooltip content="Remove Integration">
|
||||
<IconButton
|
||||
|
||||
@@ -3,6 +3,7 @@ import { faElementor } from "@fortawesome/free-brands-svg-icons";
|
||||
import {
|
||||
faAnchorLock,
|
||||
faArrowLeft,
|
||||
faBook,
|
||||
faCog,
|
||||
faKey,
|
||||
faLock,
|
||||
@@ -82,6 +83,12 @@ const SINGLE_PERMISSION_LIST = [
|
||||
icon: faTags,
|
||||
formName: "tags"
|
||||
},
|
||||
{
|
||||
title: "Audit Logs",
|
||||
subtitle: "Audit log management control",
|
||||
icon: faBook,
|
||||
formName: "audit-logs"
|
||||
},
|
||||
{
|
||||
title: "IP Allowlist",
|
||||
subtitle: "IP allowlist management control",
|
||||
|
||||
Reference in New Issue
Block a user