mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge pull request #998 from Infisical/qovery-integration
Added Qovery integration
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
|||||||
INTEGRATION_BITBUCKET_API_URL,
|
INTEGRATION_BITBUCKET_API_URL,
|
||||||
INTEGRATION_GCP_SECRET_MANAGER,
|
INTEGRATION_GCP_SECRET_MANAGER,
|
||||||
INTEGRATION_NORTHFLANK_API_URL,
|
INTEGRATION_NORTHFLANK_API_URL,
|
||||||
|
INTEGRATION_QOVERY_API_URL,
|
||||||
INTEGRATION_RAILWAY_API_URL,
|
INTEGRATION_RAILWAY_API_URL,
|
||||||
INTEGRATION_SET,
|
INTEGRATION_SET,
|
||||||
INTEGRATION_VERCEL_API_URL,
|
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
|
* Return list of Railway environments for Railway project with
|
||||||
* id [appId]
|
* id [appId]
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export const createIntegration = async (req: Request, res: Response) => {
|
|||||||
appId,
|
appId,
|
||||||
owner,
|
owner,
|
||||||
region,
|
region,
|
||||||
|
scope,
|
||||||
targetService,
|
targetService,
|
||||||
targetServiceId,
|
targetServiceId,
|
||||||
integrationAuthId,
|
integrationAuthId,
|
||||||
@@ -42,7 +43,7 @@ export const createIntegration = async (req: Request, res: Response) => {
|
|||||||
metadata
|
metadata
|
||||||
}
|
}
|
||||||
} = await validateRequest(reqValidator.CreateIntegrationV1, req);
|
} = await validateRequest(reqValidator.CreateIntegrationV1, req);
|
||||||
|
|
||||||
const integrationAuth = await IntegrationAuth.findById(integrationAuthId)
|
const integrationAuth = await IntegrationAuth.findById(integrationAuthId)
|
||||||
.populate<{ workspace: IWorkspace }>("workspace")
|
.populate<{ workspace: IWorkspace }>("workspace")
|
||||||
.select(
|
.select(
|
||||||
@@ -90,6 +91,7 @@ export const createIntegration = async (req: Request, res: Response) => {
|
|||||||
owner,
|
owner,
|
||||||
path,
|
path,
|
||||||
region,
|
region,
|
||||||
|
scope,
|
||||||
secretPath,
|
secretPath,
|
||||||
integration: integrationAuth.integration,
|
integration: integrationAuth.integration,
|
||||||
integrationAuth: new Types.ObjectId(integrationAuthId),
|
integrationAuth: new Types.ObjectId(integrationAuthId),
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ import {
|
|||||||
INTEGRATION_NETLIFY_API_URL,
|
INTEGRATION_NETLIFY_API_URL,
|
||||||
INTEGRATION_NORTHFLANK,
|
INTEGRATION_NORTHFLANK,
|
||||||
INTEGRATION_NORTHFLANK_API_URL,
|
INTEGRATION_NORTHFLANK_API_URL,
|
||||||
|
INTEGRATION_QOVERY,
|
||||||
|
INTEGRATION_QOVERY_API_URL,
|
||||||
INTEGRATION_RAILWAY,
|
INTEGRATION_RAILWAY,
|
||||||
INTEGRATION_RAILWAY_API_URL,
|
INTEGRATION_RAILWAY_API_URL,
|
||||||
INTEGRATION_RENDER,
|
INTEGRATION_RENDER,
|
||||||
@@ -219,6 +221,13 @@ const syncSecrets = async ({
|
|||||||
accessToken
|
accessToken
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case INTEGRATION_QOVERY:
|
||||||
|
await syncSecretsQovery({
|
||||||
|
integration,
|
||||||
|
secrets,
|
||||||
|
accessToken
|
||||||
|
});
|
||||||
|
break;
|
||||||
case INTEGRATION_TERRAFORM_CLOUD:
|
case INTEGRATION_TERRAFORM_CLOUD:
|
||||||
await syncSecretsTerraformCloud({
|
await syncSecretsTerraformCloud({
|
||||||
integration,
|
integration,
|
||||||
@@ -2126,6 +2135,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]
|
* Sync/push [secrets] to Terraform Cloud project with id [integration.appId]
|
||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
INTEGRATION_LARAVELFORGE,
|
INTEGRATION_LARAVELFORGE,
|
||||||
INTEGRATION_NETLIFY,
|
INTEGRATION_NETLIFY,
|
||||||
INTEGRATION_NORTHFLANK,
|
INTEGRATION_NORTHFLANK,
|
||||||
|
INTEGRATION_QOVERY,
|
||||||
INTEGRATION_RAILWAY,
|
INTEGRATION_RAILWAY,
|
||||||
INTEGRATION_RENDER,
|
INTEGRATION_RENDER,
|
||||||
INTEGRATION_SUPABASE,
|
INTEGRATION_SUPABASE,
|
||||||
@@ -45,6 +46,7 @@ export interface IIntegration {
|
|||||||
targetServiceId: string;
|
targetServiceId: string;
|
||||||
path: string;
|
path: string;
|
||||||
region: string;
|
region: string;
|
||||||
|
scope: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
integration:
|
integration:
|
||||||
| "azure-key-vault"
|
| "azure-key-vault"
|
||||||
@@ -63,6 +65,7 @@ export interface IIntegration {
|
|||||||
| "travisci"
|
| "travisci"
|
||||||
| "supabase"
|
| "supabase"
|
||||||
| "checkly"
|
| "checkly"
|
||||||
|
| "qovery"
|
||||||
| "terraform-cloud"
|
| "terraform-cloud"
|
||||||
| "teamcity"
|
| "teamcity"
|
||||||
| "hashicorp-vault"
|
| "hashicorp-vault"
|
||||||
@@ -119,11 +122,13 @@ const integrationSchema = new Schema<IIntegration>(
|
|||||||
},
|
},
|
||||||
targetService: {
|
targetService: {
|
||||||
// railway-specific service
|
// railway-specific service
|
||||||
|
// qovery-specific project
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
targetServiceId: {
|
targetServiceId: {
|
||||||
// railway-specific service
|
// railway-specific service
|
||||||
|
// qovery specific project
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
@@ -143,6 +148,11 @@ const integrationSchema = new Schema<IIntegration>(
|
|||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
scope: {
|
||||||
|
// qovery-specific scope
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
integration: {
|
integration: {
|
||||||
type: String,
|
type: String,
|
||||||
enum: [
|
enum: [
|
||||||
@@ -162,6 +172,7 @@ const integrationSchema = new Schema<IIntegration>(
|
|||||||
INTEGRATION_TRAVISCI,
|
INTEGRATION_TRAVISCI,
|
||||||
INTEGRATION_SUPABASE,
|
INTEGRATION_SUPABASE,
|
||||||
INTEGRATION_CHECKLY,
|
INTEGRATION_CHECKLY,
|
||||||
|
INTEGRATION_QOVERY,
|
||||||
INTEGRATION_TERRAFORM_CLOUD,
|
INTEGRATION_TERRAFORM_CLOUD,
|
||||||
INTEGRATION_TEAMCITY,
|
INTEGRATION_TEAMCITY,
|
||||||
INTEGRATION_HASHICORP_VAULT,
|
INTEGRATION_HASHICORP_VAULT,
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
|
|
||||||
// TODO: in the future separate metadata
|
|
||||||
// into distinct types by integration
|
|
||||||
export type Metadata = {
|
export type Metadata = {
|
||||||
secretPrefix?: string;
|
secretPrefix?: string;
|
||||||
secretSuffix?: string;
|
secretSuffix?: string;
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ import {
|
|||||||
| "aws-parameter-store"
|
| "aws-parameter-store"
|
||||||
| "aws-secret-manager"
|
| "aws-secret-manager"
|
||||||
| "checkly"
|
| "checkly"
|
||||||
|
| "qovery"
|
||||||
| "cloudflare-pages"
|
| "cloudflare-pages"
|
||||||
| "codefresh"
|
| "codefresh"
|
||||||
| "digital-ocean-app-platform"
|
| "digital-ocean-app-platform"
|
||||||
|
|||||||
@@ -60,6 +60,54 @@ router.get(
|
|||||||
integrationAuthController.getIntegrationAuthVercelBranches
|
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(
|
router.get(
|
||||||
"/:integrationAuthId/railway/environments",
|
"/:integrationAuthId/railway/environments",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
|
|||||||
@@ -76,13 +76,14 @@ export const CreateIntegrationV1 = z.object({
|
|||||||
owner: z.string().trim().optional(),
|
owner: z.string().trim().optional(),
|
||||||
path: z.string().trim().optional(),
|
path: z.string().trim().optional(),
|
||||||
region: z.string().trim().optional(),
|
region: z.string().trim().optional(),
|
||||||
|
scope: z.string().trim().optional(),
|
||||||
metadata: z.object({
|
metadata: z.object({
|
||||||
secretPrefix: z.string().optional(),
|
secretPrefix: z.string().optional(),
|
||||||
secretSuffix: z.string().optional(),
|
secretSuffix: z.string().optional(),
|
||||||
secretGCPLabel: z.object({
|
secretGCPLabel: z.object({
|
||||||
labelName: z.string(),
|
labelName: z.string(),
|
||||||
labelValue: z.string()
|
labelValue: z.string()
|
||||||
}).optional()
|
}).optional(),
|
||||||
}).optional()
|
}).optional()
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -113,6 +113,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({
|
export const GetIntegrationAuthRailwayEnvironmentsV1 = z.object({
|
||||||
params: z.object({
|
params: z.object({
|
||||||
integrationAuthId: z.string().trim()
|
integrationAuthId: z.string().trim()
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export const INTEGRATION_TRAVISCI = "travisci";
|
|||||||
export const INTEGRATION_TEAMCITY = "teamcity";
|
export const INTEGRATION_TEAMCITY = "teamcity";
|
||||||
export const INTEGRATION_SUPABASE = "supabase";
|
export const INTEGRATION_SUPABASE = "supabase";
|
||||||
export const INTEGRATION_CHECKLY = "checkly";
|
export const INTEGRATION_CHECKLY = "checkly";
|
||||||
|
export const INTEGRATION_QOVERY = "qovery";
|
||||||
export const INTEGRATION_TERRAFORM_CLOUD = "terraform-cloud";
|
export const INTEGRATION_TERRAFORM_CLOUD = "terraform-cloud";
|
||||||
export const INTEGRATION_HASHICORP_VAULT = "hashicorp-vault";
|
export const INTEGRATION_HASHICORP_VAULT = "hashicorp-vault";
|
||||||
export const INTEGRATION_CLOUDFLARE_PAGES = "cloudflare-pages";
|
export const INTEGRATION_CLOUDFLARE_PAGES = "cloudflare-pages";
|
||||||
@@ -53,6 +54,7 @@ export const INTEGRATION_SET = new Set([
|
|||||||
INTEGRATION_TEAMCITY,
|
INTEGRATION_TEAMCITY,
|
||||||
INTEGRATION_SUPABASE,
|
INTEGRATION_SUPABASE,
|
||||||
INTEGRATION_CHECKLY,
|
INTEGRATION_CHECKLY,
|
||||||
|
INTEGRATION_QOVERY,
|
||||||
INTEGRATION_TERRAFORM_CLOUD,
|
INTEGRATION_TERRAFORM_CLOUD,
|
||||||
INTEGRATION_HASHICORP_VAULT,
|
INTEGRATION_HASHICORP_VAULT,
|
||||||
INTEGRATION_CLOUDFLARE_PAGES,
|
INTEGRATION_CLOUDFLARE_PAGES,
|
||||||
@@ -94,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_SUPABASE_API_URL = "https://api.supabase.com";
|
||||||
export const INTEGRATION_LARAVELFORGE_API_URL = "https://forge.laravel.com";
|
export const INTEGRATION_LARAVELFORGE_API_URL = "https://forge.laravel.com";
|
||||||
export const INTEGRATION_CHECKLY_API_URL = "https://api.checklyhq.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_TERRAFORM_CLOUD_API_URL = "https://app.terraform.io";
|
||||||
export const INTEGRATION_CLOUDFLARE_PAGES_API_URL = "https://api.cloudflare.com";
|
export const INTEGRATION_CLOUDFLARE_PAGES_API_URL = "https://api.cloudflare.com";
|
||||||
export const INTEGRATION_BITBUCKET_API_URL = "https://api.bitbucket.org";
|
export const INTEGRATION_BITBUCKET_API_URL = "https://api.bitbucket.org";
|
||||||
@@ -273,6 +276,15 @@ export const getIntegrationOptions = async () => {
|
|||||||
clientId: "",
|
clientId: "",
|
||||||
docsLink: "",
|
docsLink: "",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Qovery",
|
||||||
|
slug: "qovery",
|
||||||
|
image: "Qovery.png",
|
||||||
|
isAvailable: true,
|
||||||
|
type: "pat",
|
||||||
|
clientId: "",
|
||||||
|
docsLink: "",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "HashiCorp Vault",
|
name: "HashiCorp Vault",
|
||||||
slug: "hashicorp-vault",
|
slug: "hashicorp-vault",
|
||||||
|
|||||||
@@ -4,6 +4,12 @@ title: "Changelog"
|
|||||||
|
|
||||||
The changelog below reflects new product developments and updates on a monthly basis.
|
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
|
## August 2023
|
||||||
|
|
||||||
- Release Audit Logs V2.
|
- 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>
|
||||||
|
|
||||||
|

|
||||||
@@ -27,6 +27,7 @@ Missing an integration? [Throw in a request](https://github.com/Infisical/infisi
|
|||||||
| [Northflank](/integrations/cloud/northflank) | Cloud | Available |
|
| [Northflank](/integrations/cloud/northflank) | Cloud | Available |
|
||||||
| [Cloudflare Pages](/integrations/cloud/cloudflare-pages) | Cloud | Available |
|
| [Cloudflare Pages](/integrations/cloud/cloudflare-pages) | Cloud | Available |
|
||||||
| [Checkly](/integrations/cloud/checkly) | Cloud | Available |
|
| [Checkly](/integrations/cloud/checkly) | Cloud | Available |
|
||||||
|
| [Qovery](/integrations/cloud/qovery) | Cloud | Available |
|
||||||
| [HashiCorp Vault](/integrations/cloud/hashicorp-vault) | Cloud | Available |
|
| [HashiCorp Vault](/integrations/cloud/hashicorp-vault) | Cloud | Available |
|
||||||
| [AWS Parameter Store](/integrations/cloud/aws-parameter-store) | Cloud | Available |
|
| [AWS Parameter Store](/integrations/cloud/aws-parameter-store) | Cloud | Available |
|
||||||
| [AWS Secrets Manager](/integrations/cloud/aws-secret-manager) | Cloud | Available |
|
| [AWS Secrets Manager](/integrations/cloud/aws-secret-manager) | Cloud | Available |
|
||||||
|
|||||||
@@ -238,6 +238,7 @@
|
|||||||
"integrations/cloud/teamcity",
|
"integrations/cloud/teamcity",
|
||||||
"integrations/cloud/cloudflare-pages",
|
"integrations/cloud/cloudflare-pages",
|
||||||
"integrations/cloud/checkly",
|
"integrations/cloud/checkly",
|
||||||
|
"integrations/cloud/qovery",
|
||||||
"integrations/cloud/hashicorp-vault",
|
"integrations/cloud/hashicorp-vault",
|
||||||
"integrations/cloud/azure-key-vault",
|
"integrations/cloud/azure-key-vault",
|
||||||
"integrations/cloud/gcp-secret-manager",
|
"integrations/cloud/gcp-secret-manager",
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const integrationSlugNameMapping: Mapping = {
|
|||||||
"travisci": "TravisCI",
|
"travisci": "TravisCI",
|
||||||
"supabase": "Supabase",
|
"supabase": "Supabase",
|
||||||
"checkly": "Checkly",
|
"checkly": "Checkly",
|
||||||
|
"qovery": "Qovery",
|
||||||
"terraform-cloud": "Terraform Cloud",
|
"terraform-cloud": "Terraform Cloud",
|
||||||
"teamcity": "TeamCity",
|
"teamcity": "TeamCity",
|
||||||
"hashicorp-vault": "Vault",
|
"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 |
@@ -9,6 +9,8 @@ import {
|
|||||||
Environment,
|
Environment,
|
||||||
IntegrationAuth,
|
IntegrationAuth,
|
||||||
NorthflankSecretGroup,
|
NorthflankSecretGroup,
|
||||||
|
Org,
|
||||||
|
Project,
|
||||||
Service,
|
Service,
|
||||||
Team,
|
Team,
|
||||||
TeamCityBuildConfig} from "./types";
|
TeamCityBuildConfig} from "./types";
|
||||||
@@ -27,6 +29,31 @@ const integrationAuthKeys = {
|
|||||||
integrationAuthId: string;
|
integrationAuthId: string;
|
||||||
appId: string;
|
appId: string;
|
||||||
}) => [{ integrationAuthId, appId }, "integrationAuthVercelBranches"] as const,
|
}) => [{ 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: ({
|
getIntegrationAuthRailwayEnvironments: ({
|
||||||
integrationAuthId,
|
integrationAuthId,
|
||||||
appId
|
appId
|
||||||
@@ -120,6 +147,121 @@ const fetchIntegrationAuthVercelBranches = async ({
|
|||||||
return branches;
|
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 ({
|
const fetchIntegrationAuthRailwayEnvironments = async ({
|
||||||
integrationAuthId,
|
integrationAuthId,
|
||||||
appId
|
appId
|
||||||
@@ -271,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 = ({
|
export const useGetIntegrationAuthRailwayEnvironments = ({
|
||||||
integrationAuthId,
|
integrationAuthId,
|
||||||
appId
|
appId
|
||||||
|
|||||||
@@ -26,6 +26,21 @@ export type Environment = {
|
|||||||
environmentId: string;
|
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 = {
|
export type Service = {
|
||||||
name: string;
|
name: string;
|
||||||
serviceId: string;
|
serviceId: string;
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export const useCreateIntegration = () => {
|
|||||||
owner,
|
owner,
|
||||||
path,
|
path,
|
||||||
region,
|
region,
|
||||||
|
scope,
|
||||||
secretPath,
|
secretPath,
|
||||||
metadata
|
metadata
|
||||||
}: {
|
}: {
|
||||||
@@ -56,6 +57,7 @@ export const useCreateIntegration = () => {
|
|||||||
owner?: string;
|
owner?: string;
|
||||||
path?: string;
|
path?: string;
|
||||||
region?: string;
|
region?: string;
|
||||||
|
scope?: string;
|
||||||
metadata?: {
|
metadata?: {
|
||||||
secretPrefix?: string;
|
secretPrefix?: string;
|
||||||
secretSuffix?: string;
|
secretSuffix?: string;
|
||||||
@@ -73,6 +75,7 @@ export const useCreateIntegration = () => {
|
|||||||
targetServiceId,
|
targetServiceId,
|
||||||
owner,
|
owner,
|
||||||
path,
|
path,
|
||||||
|
scope,
|
||||||
region,
|
region,
|
||||||
secretPath,
|
secretPath,
|
||||||
metadata
|
metadata
|
||||||
|
|||||||
@@ -32,5 +32,9 @@ export type TIntegration = {
|
|||||||
__v: number;
|
__v: number;
|
||||||
metadata?: {
|
metadata?: {
|
||||||
secretSuffix?: string;
|
secretSuffix?: string;
|
||||||
|
scope: string;
|
||||||
|
org: string;
|
||||||
|
project: string;
|
||||||
|
environment: string;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
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;
|
||||||
@@ -87,6 +87,9 @@ export const redirectForProviderAuth = (integrationOption: TCloudIntegration) =>
|
|||||||
case "checkly":
|
case "checkly":
|
||||||
link = `${window.location.origin}/integrations/checkly/authorize`;
|
link = `${window.location.origin}/integrations/checkly/authorize`;
|
||||||
break;
|
break;
|
||||||
|
case "qovery":
|
||||||
|
link = `${window.location.origin}/integrations/qovery/authorize`;
|
||||||
|
break;
|
||||||
case "railway":
|
case "railway":
|
||||||
link = `${window.location.origin}/integrations/railway/authorize`;
|
link = `${window.location.origin}/integrations/railway/authorize`;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -96,8 +96,30 @@ export const IntegrationsSection = ({
|
|||||||
{integrationSlugNameMapping[integration.integration]}
|
{integrationSlugNameMapping[integration.integration]}
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<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">
|
<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.integration === "hashicorp-vault"
|
||||||
? `${integration.app} - path: ${integration.path}`
|
? `${integration.app} - path: ${integration.path}`
|
||||||
@@ -131,7 +153,7 @@ export const IntegrationsSection = ({
|
|||||||
I={ProjectPermissionActions.Delete}
|
I={ProjectPermissionActions.Delete}
|
||||||
a={ProjectPermissionSub.Integrations}
|
a={ProjectPermissionSub.Integrations}
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
{(isAllowed: boolean) => (
|
||||||
<div className="ml-2 opacity-80 duration-200 hover:opacity-100">
|
<div className="ml-2 opacity-80 duration-200 hover:opacity-100">
|
||||||
<Tooltip content="Remove Integration">
|
<Tooltip content="Remove Integration">
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|||||||
Reference in New Issue
Block a user