mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 10:28:22 +00:00
feat: support render environment groups (#4327)
* feat: support env groups in render sync * fix: update doc * Update backend/src/services/app-connection/render/render-connection-service.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * fix: pr changes * fix: lint and type check * fix: changes * fix: remove secrets * fix: MAX iterations in render sync * fix: render sync review fields * fix: pr changes * fix: lint * fix: changes --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
greptile-apps[bot]
parent
7748e03612
commit
461deef0d5
@@ -2491,6 +2491,7 @@ export const SecretSyncs = {
|
|||||||
},
|
},
|
||||||
RENDER: {
|
RENDER: {
|
||||||
serviceId: "The ID of the Render service to sync secrets to.",
|
serviceId: "The ID of the Render service to sync secrets to.",
|
||||||
|
environmentGroupId: "The ID of the Render environment group to sync secrets to.",
|
||||||
scope: "The Render scope that secrets should be synced to.",
|
scope: "The Render scope that secrets should be synced to.",
|
||||||
type: "The Render resource type to sync secrets to."
|
type: "The Render resource type to sync secrets to."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -49,4 +49,32 @@ export const registerRenderConnectionRouter = async (server: FastifyZodProvider)
|
|||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: `/:connectionId/environment-groups`,
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
connectionId: z.string().uuid()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z
|
||||||
|
.object({
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { connectionId } = req.params;
|
||||||
|
const groups = await server.services.appConnection.render.listEnvironmentGroups(connectionId, req.permission);
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ import { IntegrationUrls } from "@app/services/integration-auth/integration-list
|
|||||||
import { AppConnection } from "../app-connection-enums";
|
import { AppConnection } from "../app-connection-enums";
|
||||||
import { RenderConnectionMethod } from "./render-connection-enums";
|
import { RenderConnectionMethod } from "./render-connection-enums";
|
||||||
import {
|
import {
|
||||||
|
TRawRenderEnvironmentGroup,
|
||||||
TRawRenderService,
|
TRawRenderService,
|
||||||
TRenderConnection,
|
TRenderConnection,
|
||||||
TRenderConnectionConfig,
|
TRenderConnectionConfig,
|
||||||
|
TRenderEnvironmentGroup,
|
||||||
TRenderService
|
TRenderService
|
||||||
} from "./render-connection-types";
|
} from "./render-connection-types";
|
||||||
|
|
||||||
@@ -32,7 +34,11 @@ export const listRenderServices = async (appConnection: TRenderConnection): Prom
|
|||||||
const perPage = 100;
|
const perPage = 100;
|
||||||
let cursor;
|
let cursor;
|
||||||
|
|
||||||
|
let maxIterations = 10;
|
||||||
|
|
||||||
while (hasMorePages) {
|
while (hasMorePages) {
|
||||||
|
if (maxIterations <= 0) break;
|
||||||
|
|
||||||
const res: TRawRenderService[] = (
|
const res: TRawRenderService[] = (
|
||||||
await request.get<TRawRenderService[]>(`${IntegrationUrls.RENDER_API_URL}/v1/services`, {
|
await request.get<TRawRenderService[]>(`${IntegrationUrls.RENDER_API_URL}/v1/services`, {
|
||||||
params: new URLSearchParams({
|
params: new URLSearchParams({
|
||||||
@@ -59,6 +65,8 @@ export const listRenderServices = async (appConnection: TRenderConnection): Prom
|
|||||||
} else {
|
} else {
|
||||||
cursor = res[res.length - 1].cursor;
|
cursor = res[res.length - 1].cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
maxIterations -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
@@ -86,3 +94,52 @@ export const validateRenderConnectionCredentials = async (config: TRenderConnect
|
|||||||
|
|
||||||
return inputCredentials;
|
return inputCredentials;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const listRenderEnvironmentGroups = async (
|
||||||
|
appConnection: TRenderConnection
|
||||||
|
): Promise<TRenderEnvironmentGroup[]> => {
|
||||||
|
const {
|
||||||
|
credentials: { apiKey }
|
||||||
|
} = appConnection;
|
||||||
|
|
||||||
|
const groups: TRenderEnvironmentGroup[] = [];
|
||||||
|
let hasMorePages = true;
|
||||||
|
const perPage = 100;
|
||||||
|
let cursor;
|
||||||
|
let maxIterations = 10;
|
||||||
|
|
||||||
|
while (hasMorePages) {
|
||||||
|
if (maxIterations <= 0) break;
|
||||||
|
|
||||||
|
const res: TRawRenderEnvironmentGroup[] = (
|
||||||
|
await request.get<TRawRenderEnvironmentGroup[]>(`${IntegrationUrls.RENDER_API_URL}/v1/env-groups`, {
|
||||||
|
params: new URLSearchParams({
|
||||||
|
...(cursor ? { cursor: String(cursor) } : {}),
|
||||||
|
limit: String(perPage)
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${apiKey}`,
|
||||||
|
Accept: "application/json",
|
||||||
|
"Accept-Encoding": "application/json"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).data;
|
||||||
|
|
||||||
|
res.forEach((item) => {
|
||||||
|
groups.push({
|
||||||
|
name: item.envGroup.name,
|
||||||
|
id: item.envGroup.id
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.length < perPage) {
|
||||||
|
hasMorePages = false;
|
||||||
|
} else {
|
||||||
|
cursor = res[res.length - 1].cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
maxIterations -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { logger } from "@app/lib/logger";
|
|||||||
import { OrgServiceActor } from "@app/lib/types";
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
|
|
||||||
import { AppConnection } from "../app-connection-enums";
|
import { AppConnection } from "../app-connection-enums";
|
||||||
import { listRenderServices } from "./render-connection-fns";
|
import { listRenderEnvironmentGroups, listRenderServices } from "./render-connection-fns";
|
||||||
import { TRenderConnection } from "./render-connection-types";
|
import { TRenderConnection } from "./render-connection-types";
|
||||||
|
|
||||||
type TGetAppConnectionFunc = (
|
type TGetAppConnectionFunc = (
|
||||||
@@ -24,7 +24,20 @@ export const renderConnectionService = (getAppConnection: TGetAppConnectionFunc)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const listEnvironmentGroups = async (connectionId: string, actor: OrgServiceActor) => {
|
||||||
|
const appConnection = await getAppConnection(AppConnection.Render, connectionId, actor);
|
||||||
|
try {
|
||||||
|
const groups = await listRenderEnvironmentGroups(appConnection);
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error, "Failed to list environment groups for Render connection");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
listServices
|
listServices,
|
||||||
|
listEnvironmentGroups
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -33,3 +33,16 @@ export type TRawRenderService = {
|
|||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TRenderEnvironmentGroup = {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TRawRenderEnvironmentGroup = {
|
||||||
|
cursor: string;
|
||||||
|
envGroup: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export enum RenderSyncScope {
|
export enum RenderSyncScope {
|
||||||
Service = "service"
|
Service = "service",
|
||||||
|
EnvironmentGroup = "environment-group"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum RenderSyncType {
|
export enum RenderSyncType {
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
import { isAxiosError } from "axios";
|
import { AxiosRequestConfig, isAxiosError } from "axios";
|
||||||
|
|
||||||
import { request } from "@app/lib/config/request";
|
import { request } from "@app/lib/config/request";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
||||||
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
|
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
|
||||||
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
|
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
|
||||||
|
|
||||||
|
import { RenderSyncScope } from "./render-sync-enums";
|
||||||
import { TRenderSecret, TRenderSyncWithCredentials } from "./render-sync-types";
|
import { TRenderSecret, TRenderSyncWithCredentials } from "./render-sync-types";
|
||||||
|
|
||||||
const MAX_RETRIES = 5;
|
const MAX_RETRIES = 5;
|
||||||
@@ -27,6 +29,80 @@ const makeRequestWithRetry = async <T>(requestFn: () => Promise<T>, attempt = 0)
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function getSecrets(input: { destination: TRenderSyncWithCredentials["destinationConfig"]; token: string }) {
|
||||||
|
const req: AxiosRequestConfig = {
|
||||||
|
baseURL: `${IntegrationUrls.RENDER_API_URL}/v1`,
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${input.token}`,
|
||||||
|
Accept: "application/json"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (input.destination.scope) {
|
||||||
|
case RenderSyncScope.Service: {
|
||||||
|
req.url = `/services/${input.destination.serviceId}/env-vars`;
|
||||||
|
|
||||||
|
const allSecrets: TRenderSecret[] = [];
|
||||||
|
let cursor: string | undefined;
|
||||||
|
|
||||||
|
do {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
||||||
|
const { data } = await makeRequestWithRetry(() =>
|
||||||
|
request.request<
|
||||||
|
{
|
||||||
|
envVar: {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
cursor: string;
|
||||||
|
}[]
|
||||||
|
>({
|
||||||
|
...req,
|
||||||
|
params: {
|
||||||
|
cursor
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const secrets = data.map((item) => ({
|
||||||
|
key: item.envVar.key,
|
||||||
|
value: item.envVar.value
|
||||||
|
}));
|
||||||
|
|
||||||
|
allSecrets.push(...secrets);
|
||||||
|
|
||||||
|
if (data.length > 0 && data[data.length - 1]?.cursor) {
|
||||||
|
cursor = data[data.length - 1].cursor;
|
||||||
|
} else {
|
||||||
|
cursor = undefined;
|
||||||
|
}
|
||||||
|
} while (cursor);
|
||||||
|
|
||||||
|
return allSecrets;
|
||||||
|
}
|
||||||
|
case RenderSyncScope.EnvironmentGroup: {
|
||||||
|
req.url = `/env-groups/${input.destination.environmentGroupId}`;
|
||||||
|
|
||||||
|
const res = await makeRequestWithRetry(() =>
|
||||||
|
request.request<{
|
||||||
|
envVars: {
|
||||||
|
key: string;
|
||||||
|
value: string;
|
||||||
|
}[];
|
||||||
|
}>(req)
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.data.envVars.map((item) => ({
|
||||||
|
key: item.key,
|
||||||
|
value: item.value
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new BadRequestError({ message: "Unknown render sync destination scope" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const getRenderEnvironmentSecrets = async (secretSync: TRenderSyncWithCredentials): Promise<TRenderSecret[]> => {
|
const getRenderEnvironmentSecrets = async (secretSync: TRenderSyncWithCredentials): Promise<TRenderSecret[]> => {
|
||||||
const {
|
const {
|
||||||
destinationConfig,
|
destinationConfig,
|
||||||
@@ -35,45 +111,12 @@ const getRenderEnvironmentSecrets = async (secretSync: TRenderSyncWithCredential
|
|||||||
}
|
}
|
||||||
} = secretSync;
|
} = secretSync;
|
||||||
|
|
||||||
const baseUrl = `${IntegrationUrls.RENDER_API_URL}/v1/services/${destinationConfig.serviceId}/env-vars`;
|
const secrets = await getSecrets({
|
||||||
const allSecrets: TRenderSecret[] = [];
|
destination: destinationConfig,
|
||||||
let cursor: string | undefined;
|
token: apiKey
|
||||||
|
});
|
||||||
|
|
||||||
do {
|
return secrets;
|
||||||
const url = cursor ? `${baseUrl}?cursor=${cursor}` : baseUrl;
|
|
||||||
|
|
||||||
const { data } = await makeRequestWithRetry(() =>
|
|
||||||
request.get<
|
|
||||||
{
|
|
||||||
envVar: {
|
|
||||||
key: string;
|
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
cursor: string;
|
|
||||||
}[]
|
|
||||||
>(url, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${apiKey}`,
|
|
||||||
Accept: "application/json"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const secrets = data.map((item) => ({
|
|
||||||
key: item.envVar.key,
|
|
||||||
value: item.envVar.value
|
|
||||||
}));
|
|
||||||
|
|
||||||
allSecrets.push(...secrets);
|
|
||||||
|
|
||||||
if (data.length > 0 && data[data.length - 1]?.cursor) {
|
|
||||||
cursor = data[data.length - 1].cursor;
|
|
||||||
} else {
|
|
||||||
cursor = undefined;
|
|
||||||
}
|
|
||||||
} while (cursor);
|
|
||||||
|
|
||||||
return allSecrets;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const batchUpdateEnvironmentSecrets = async (
|
const batchUpdateEnvironmentSecrets = async (
|
||||||
@@ -87,14 +130,91 @@ const batchUpdateEnvironmentSecrets = async (
|
|||||||
}
|
}
|
||||||
} = secretSync;
|
} = secretSync;
|
||||||
|
|
||||||
await makeRequestWithRetry(() =>
|
const req: AxiosRequestConfig = {
|
||||||
request.put(`${IntegrationUrls.RENDER_API_URL}/v1/services/${destinationConfig.serviceId}/env-vars`, envVars, {
|
baseURL: `${IntegrationUrls.RENDER_API_URL}/v1`,
|
||||||
headers: {
|
method: "PUT",
|
||||||
Authorization: `Bearer ${apiKey}`,
|
headers: {
|
||||||
Accept: "application/json"
|
Authorization: `Bearer ${apiKey}`,
|
||||||
|
Accept: "application/json"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (destinationConfig.scope) {
|
||||||
|
case RenderSyncScope.Service: {
|
||||||
|
await makeRequestWithRetry(() =>
|
||||||
|
request.request({
|
||||||
|
...req,
|
||||||
|
url: `/services/${destinationConfig.serviceId}/env-vars`,
|
||||||
|
data: envVars
|
||||||
|
})
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case RenderSyncScope.EnvironmentGroup: {
|
||||||
|
for await (const variable of envVars) {
|
||||||
|
await makeRequestWithRetry(() =>
|
||||||
|
request.request({
|
||||||
|
...req,
|
||||||
|
url: `/env-groups/${destinationConfig.environmentGroupId}/env-vars/${variable.key}`,
|
||||||
|
data: {
|
||||||
|
value: variable.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
})
|
break;
|
||||||
);
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new BadRequestError({ message: "Unknown render sync destination scope" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteEnvironmentSecret = async (
|
||||||
|
secretSync: TRenderSyncWithCredentials,
|
||||||
|
envVar: { key: string; value: string }
|
||||||
|
): Promise<void> => {
|
||||||
|
const {
|
||||||
|
destinationConfig,
|
||||||
|
connection: {
|
||||||
|
credentials: { apiKey }
|
||||||
|
}
|
||||||
|
} = secretSync;
|
||||||
|
|
||||||
|
const req: AxiosRequestConfig = {
|
||||||
|
baseURL: `${IntegrationUrls.RENDER_API_URL}/v1`,
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${apiKey}`,
|
||||||
|
Accept: "application/json"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (destinationConfig.scope) {
|
||||||
|
case RenderSyncScope.Service: {
|
||||||
|
await makeRequestWithRetry(() =>
|
||||||
|
request.request({
|
||||||
|
...req,
|
||||||
|
url: `/services/${destinationConfig.serviceId}/env-vars/${envVar.key}`
|
||||||
|
})
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case RenderSyncScope.EnvironmentGroup: {
|
||||||
|
await makeRequestWithRetry(() =>
|
||||||
|
request.request({
|
||||||
|
...req,
|
||||||
|
url: `/env-groups/${destinationConfig.environmentGroupId}/env-vars/${envVar.key}`
|
||||||
|
})
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new BadRequestError({ message: "Unknown render sync destination scope" });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const redeployService = async (secretSync: TRenderSyncWithCredentials) => {
|
const redeployService = async (secretSync: TRenderSyncWithCredentials) => {
|
||||||
@@ -105,18 +225,50 @@ const redeployService = async (secretSync: TRenderSyncWithCredentials) => {
|
|||||||
}
|
}
|
||||||
} = secretSync;
|
} = secretSync;
|
||||||
|
|
||||||
await makeRequestWithRetry(() =>
|
const req: AxiosRequestConfig = {
|
||||||
request.post(
|
baseURL: `${IntegrationUrls.RENDER_API_URL}/v1`,
|
||||||
`${IntegrationUrls.RENDER_API_URL}/v1/services/${destinationConfig.serviceId}/deploys`,
|
headers: {
|
||||||
{},
|
Authorization: `Bearer ${apiKey}`,
|
||||||
{
|
Accept: "application/json"
|
||||||
headers: {
|
}
|
||||||
Authorization: `Bearer ${apiKey}`,
|
};
|
||||||
Accept: "application/json"
|
|
||||||
}
|
switch (destinationConfig.scope) {
|
||||||
|
case RenderSyncScope.Service: {
|
||||||
|
await makeRequestWithRetry(() =>
|
||||||
|
request.request({
|
||||||
|
...req,
|
||||||
|
method: "POST",
|
||||||
|
url: `/services/${destinationConfig.serviceId}/deploys`,
|
||||||
|
data: {}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case RenderSyncScope.EnvironmentGroup: {
|
||||||
|
const { data } = await request.request<{ serviceLinks: { id: string }[] }>({
|
||||||
|
...req,
|
||||||
|
method: "GET",
|
||||||
|
url: `/env-groups/${destinationConfig.environmentGroupId}`
|
||||||
|
});
|
||||||
|
|
||||||
|
for await (const link of data.serviceLinks) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
||||||
|
await makeRequestWithRetry(() =>
|
||||||
|
request.request({
|
||||||
|
...req,
|
||||||
|
url: `/services/${link.id}/deploys`,
|
||||||
|
data: {}
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
)
|
break;
|
||||||
);
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new BadRequestError({ message: "Unknown render sync destination scope" });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RenderSyncFns = {
|
export const RenderSyncFns = {
|
||||||
@@ -169,14 +321,15 @@ export const RenderSyncFns = {
|
|||||||
const finalEnvVars: Array<{ key: string; value: string }> = [];
|
const finalEnvVars: Array<{ key: string; value: string }> = [];
|
||||||
|
|
||||||
for (const renderSecret of renderSecrets) {
|
for (const renderSecret of renderSecrets) {
|
||||||
if (!(renderSecret.key in secretMap)) {
|
if (renderSecret.key in secretMap) {
|
||||||
finalEnvVars.push({
|
finalEnvVars.push({
|
||||||
key: renderSecret.key,
|
key: renderSecret.key,
|
||||||
value: renderSecret.value
|
value: renderSecret.value
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await batchUpdateEnvironmentSecrets(secretSync, finalEnvVars);
|
|
||||||
|
await Promise.all(finalEnvVars.map((el) => deleteEnvironmentSecret(secretSync, el)));
|
||||||
|
|
||||||
if (secretSync.syncOptions.autoRedeployServices) {
|
if (secretSync.syncOptions.autoRedeployServices) {
|
||||||
await redeployService(secretSync);
|
await redeployService(secretSync);
|
||||||
|
|||||||
@@ -17,6 +17,14 @@ const RenderSyncDestinationConfigSchema = z.discriminatedUnion("scope", [
|
|||||||
scope: z.literal(RenderSyncScope.Service).describe(SecretSyncs.DESTINATION_CONFIG.RENDER.scope),
|
scope: z.literal(RenderSyncScope.Service).describe(SecretSyncs.DESTINATION_CONFIG.RENDER.scope),
|
||||||
serviceId: z.string().min(1, "Service ID is required").describe(SecretSyncs.DESTINATION_CONFIG.RENDER.serviceId),
|
serviceId: z.string().min(1, "Service ID is required").describe(SecretSyncs.DESTINATION_CONFIG.RENDER.serviceId),
|
||||||
type: z.nativeEnum(RenderSyncType).describe(SecretSyncs.DESTINATION_CONFIG.RENDER.type)
|
type: z.nativeEnum(RenderSyncType).describe(SecretSyncs.DESTINATION_CONFIG.RENDER.type)
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
scope: z.literal(RenderSyncScope.EnvironmentGroup).describe(SecretSyncs.DESTINATION_CONFIG.RENDER.scope),
|
||||||
|
environmentGroupId: z
|
||||||
|
.string()
|
||||||
|
.min(1, "Environment Group ID is required")
|
||||||
|
.describe(SecretSyncs.DESTINATION_CONFIG.RENDER.environmentGroupId),
|
||||||
|
type: z.nativeEnum(RenderSyncType).describe(SecretSyncs.DESTINATION_CONFIG.RENDER.type)
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -30,8 +30,9 @@ description: "Learn how to configure a Render Sync for Infisical."
|
|||||||

|

|
||||||
|
|
||||||
- **Render Connection**: The Render Connection to authenticate with.
|
- **Render Connection**: The Render Connection to authenticate with.
|
||||||
- **Scope**: Select **Service**.
|
- **Scope**: Select **Service** or **Environment Group**.
|
||||||
- **Service**: Choose the Render service you want to sync secrets to.
|
- **Service**: Choose the Render service you want to sync secrets to.
|
||||||
|
- **Environment Group**: Choose the Render environment group you want to sync secrets to.
|
||||||
|
|
||||||
5. Configure the **Sync Options** to specify how secrets should be synced, then click **Next**.
|
5. Configure the **Sync Options** to specify how secrets should be synced, then click **Next**.
|
||||||

|

|
||||||
|
|||||||
+70
-24
@@ -5,7 +5,9 @@ import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/Se
|
|||||||
import { FilterableSelect, FormControl, Select, SelectItem } from "@app/components/v2";
|
import { FilterableSelect, FormControl, Select, SelectItem } from "@app/components/v2";
|
||||||
import { RENDER_SYNC_SCOPES } from "@app/helpers/secretSyncs";
|
import { RENDER_SYNC_SCOPES } from "@app/helpers/secretSyncs";
|
||||||
import {
|
import {
|
||||||
|
TRenderEnvironmentGroup,
|
||||||
TRenderService,
|
TRenderService,
|
||||||
|
useRenderConnectionListEnvironmentGroups,
|
||||||
useRenderConnectionListServices
|
useRenderConnectionListServices
|
||||||
} from "@app/hooks/api/appConnections/render";
|
} from "@app/hooks/api/appConnections/render";
|
||||||
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
@@ -19,6 +21,7 @@ export const RenderSyncFields = () => {
|
|||||||
>();
|
>();
|
||||||
|
|
||||||
const connectionId = useWatch({ name: "connection.id", control });
|
const connectionId = useWatch({ name: "connection.id", control });
|
||||||
|
const selectedScope = useWatch({ name: "destinationConfig.scope", control });
|
||||||
|
|
||||||
const { data: services = [], isPending: isServicesPending } = useRenderConnectionListServices(
|
const { data: services = [], isPending: isServicesPending } = useRenderConnectionListServices(
|
||||||
connectionId,
|
connectionId,
|
||||||
@@ -27,11 +30,17 @@ export const RenderSyncFields = () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { data: groups = [], isPending: isGroupsPending } =
|
||||||
|
useRenderConnectionListEnvironmentGroups(connectionId, {
|
||||||
|
enabled: Boolean(connectionId) && selectedScope === RenderSyncScope.EnvironmentGroup
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SecretSyncConnectionField
|
<SecretSyncConnectionField
|
||||||
onChange={() => {
|
onChange={() => {
|
||||||
setValue("destinationConfig.serviceId", "");
|
setValue("destinationConfig.serviceId", "");
|
||||||
|
setValue("destinationConfig.environmentGroupId", "");
|
||||||
setValue("destinationConfig.type", RenderSyncType.Env);
|
setValue("destinationConfig.type", RenderSyncType.Env);
|
||||||
setValue("destinationConfig.scope", RenderSyncScope.Service);
|
setValue("destinationConfig.scope", RenderSyncScope.Service);
|
||||||
}}
|
}}
|
||||||
@@ -83,30 +92,67 @@ export const RenderSyncFields = () => {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Controller
|
{selectedScope === RenderSyncScope.Service && (
|
||||||
name="destinationConfig.serviceId"
|
<Controller
|
||||||
control={control}
|
name="destinationConfig.serviceId"
|
||||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
control={control}
|
||||||
<FormControl errorText={error?.message} isError={Boolean(error?.message)} label="Service">
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
<FilterableSelect
|
<FormControl
|
||||||
isLoading={isServicesPending && Boolean(connectionId)}
|
errorText={error?.message}
|
||||||
isDisabled={!connectionId}
|
isError={Boolean(error?.message)}
|
||||||
value={services ? (services.find((service) => service.id === value) ?? []) : []}
|
label="Service"
|
||||||
onChange={(option) => {
|
>
|
||||||
onChange((option as SingleValue<TRenderService>)?.id ?? null);
|
<FilterableSelect
|
||||||
setValue(
|
isLoading={isServicesPending && Boolean(connectionId)}
|
||||||
"destinationConfig.serviceName",
|
isDisabled={!connectionId}
|
||||||
(option as SingleValue<TRenderService>)?.name ?? ""
|
value={services ? (services.find((service) => service.id === value) ?? []) : []}
|
||||||
);
|
onChange={(option) => {
|
||||||
}}
|
onChange((option as SingleValue<TRenderService>)?.id ?? null);
|
||||||
options={services}
|
setValue(
|
||||||
placeholder="Select a service..."
|
"destinationConfig.serviceName",
|
||||||
getOptionLabel={(option) => option.name}
|
(option as SingleValue<TRenderService>)?.name ?? ""
|
||||||
getOptionValue={(option) => option.id.toString()}
|
);
|
||||||
/>
|
}}
|
||||||
</FormControl>
|
options={services}
|
||||||
)}
|
placeholder="Select a service..."
|
||||||
/>
|
getOptionLabel={(option) => option.name}
|
||||||
|
getOptionValue={(option) => option.id.toString()}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{selectedScope === RenderSyncScope.EnvironmentGroup && (
|
||||||
|
<Controller
|
||||||
|
name="destinationConfig.environmentGroupId"
|
||||||
|
control={control}
|
||||||
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
label="Environment Group"
|
||||||
|
>
|
||||||
|
<FilterableSelect
|
||||||
|
isLoading={isGroupsPending && Boolean(connectionId)}
|
||||||
|
isDisabled={!connectionId}
|
||||||
|
value={groups ? (groups.find((g) => g.id === value) ?? []) : []}
|
||||||
|
onChange={(option) => {
|
||||||
|
onChange((option as SingleValue<TRenderEnvironmentGroup>)?.id ?? null);
|
||||||
|
setValue(
|
||||||
|
"destinationConfig.environmentGroupName",
|
||||||
|
(option as SingleValue<TRenderEnvironmentGroup>)?.name ?? ""
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
options={groups}
|
||||||
|
placeholder="Select an environment group..."
|
||||||
|
getOptionLabel={(option) => option.name}
|
||||||
|
getOptionValue={(option) => option.id.toString()}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+12
-4
@@ -4,6 +4,7 @@ import { GenericFieldLabel } from "@app/components/secret-syncs";
|
|||||||
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
|
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
|
||||||
import { Badge } from "@app/components/v2";
|
import { Badge } from "@app/components/v2";
|
||||||
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||||
|
import { RenderSyncScope } from "@app/hooks/api/secretSyncs/types/render-sync";
|
||||||
|
|
||||||
export const RenderSyncOptionsReviewFields = () => {
|
export const RenderSyncOptionsReviewFields = () => {
|
||||||
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.Render }>();
|
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.Render }>();
|
||||||
@@ -27,13 +28,20 @@ export const RenderSyncOptionsReviewFields = () => {
|
|||||||
|
|
||||||
export const RenderSyncReviewFields = () => {
|
export const RenderSyncReviewFields = () => {
|
||||||
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.Render }>();
|
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.Render }>();
|
||||||
const serviceName = watch("destinationConfig.serviceName");
|
const config = watch("destinationConfig");
|
||||||
const scope = watch("destinationConfig.scope");
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<GenericFieldLabel label="Scope">{scope}</GenericFieldLabel>
|
<GenericFieldLabel label="Scope">{config.scope}</GenericFieldLabel>
|
||||||
<GenericFieldLabel label="Service">{serviceName}</GenericFieldLabel>
|
{config.scope === RenderSyncScope.Service ? (
|
||||||
|
<GenericFieldLabel label="Service">
|
||||||
|
{config.serviceName ?? config.serviceId}
|
||||||
|
</GenericFieldLabel>
|
||||||
|
) : (
|
||||||
|
<GenericFieldLabel label="Service">
|
||||||
|
{config.environmentGroupName ?? config.environmentGroupId}
|
||||||
|
</GenericFieldLabel>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ export const RenderSyncDestinationSchema = BaseSecretSyncSchema(
|
|||||||
serviceId: z.string().trim().min(1, "Service is required"),
|
serviceId: z.string().trim().min(1, "Service is required"),
|
||||||
serviceName: z.string().trim().optional(),
|
serviceName: z.string().trim().optional(),
|
||||||
type: z.nativeEnum(RenderSyncType)
|
type: z.nativeEnum(RenderSyncType)
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
scope: z.literal(RenderSyncScope.EnvironmentGroup),
|
||||||
|
environmentGroupId: z.string().trim().min(1, "Environment Group ID is required"),
|
||||||
|
environmentGroupName: z.string().trim().optional(),
|
||||||
|
type: z.nativeEnum(RenderSyncType)
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -212,5 +212,9 @@ export const RENDER_SYNC_SCOPES: Record<RenderSyncScope, { name: string; descrip
|
|||||||
[RenderSyncScope.Service]: {
|
[RenderSyncScope.Service]: {
|
||||||
name: "Service",
|
name: "Service",
|
||||||
description: "Infisical will sync secrets to the specified Render service."
|
description: "Infisical will sync secrets to the specified Render service."
|
||||||
|
},
|
||||||
|
[RenderSyncScope.EnvironmentGroup]: {
|
||||||
|
name: "EnvironmentGroup",
|
||||||
|
description: "Infisical will sync secrets to the specified Render environment group."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import { useQuery, UseQueryOptions } from "@tanstack/react-query";
|
|||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
import { appConnectionKeys } from "../queries";
|
import { appConnectionKeys } from "../queries";
|
||||||
import { TRenderService } from "./types";
|
import { TRenderEnvironmentGroup, TRenderService } from "./types";
|
||||||
|
|
||||||
const renderConnectionKeys = {
|
const renderConnectionKeys = {
|
||||||
all: [...appConnectionKeys.all, "render"] as const,
|
all: [...appConnectionKeys.all, "render"] as const,
|
||||||
listServices: (connectionId: string) =>
|
listServices: (connectionId: string) =>
|
||||||
[...renderConnectionKeys.all, "services", connectionId] as const
|
[...renderConnectionKeys.all, "services", connectionId] as const,
|
||||||
|
listEnvironmentGroups: (connectionId: string) =>
|
||||||
|
[...renderConnectionKeys.all, "environment-groups", connectionId] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useRenderConnectionListServices = (
|
export const useRenderConnectionListServices = (
|
||||||
@@ -35,3 +37,28 @@ export const useRenderConnectionListServices = (
|
|||||||
...options
|
...options
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useRenderConnectionListEnvironmentGroups = (
|
||||||
|
connectionId: string,
|
||||||
|
options?: Omit<
|
||||||
|
UseQueryOptions<
|
||||||
|
TRenderEnvironmentGroup[],
|
||||||
|
unknown,
|
||||||
|
TRenderEnvironmentGroup[],
|
||||||
|
ReturnType<typeof renderConnectionKeys.listEnvironmentGroups>
|
||||||
|
>,
|
||||||
|
"queryKey" | "queryFn"
|
||||||
|
>
|
||||||
|
) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: renderConnectionKeys.listEnvironmentGroups(connectionId),
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await apiRequest.get<TRenderEnvironmentGroup[]>(
|
||||||
|
`/api/v1/app-connections/render/${connectionId}/environment-groups`
|
||||||
|
);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -2,3 +2,8 @@ export type TRenderService = {
|
|||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TRenderEnvironmentGroup = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|||||||
@@ -4,12 +4,19 @@ import { RootSyncOptions, TRootSecretSync } from "@app/hooks/api/secretSyncs/typ
|
|||||||
|
|
||||||
export type TRenderSync = TRootSecretSync & {
|
export type TRenderSync = TRootSecretSync & {
|
||||||
destination: SecretSync.Render;
|
destination: SecretSync.Render;
|
||||||
destinationConfig: {
|
destinationConfig:
|
||||||
scope: RenderSyncScope.Service;
|
| {
|
||||||
type: RenderSyncType;
|
type: RenderSyncType;
|
||||||
serviceId: string;
|
scope: RenderSyncScope.Service;
|
||||||
serviceName?: string;
|
serviceId: string;
|
||||||
};
|
serviceName?: string | undefined;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: RenderSyncType;
|
||||||
|
scope: RenderSyncScope.EnvironmentGroup;
|
||||||
|
environmentGroupId: string;
|
||||||
|
environmentGroupName?: string | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
connection: {
|
connection: {
|
||||||
app: AppConnection.Render;
|
app: AppConnection.Render;
|
||||||
@@ -23,7 +30,8 @@ export type TRenderSync = TRootSecretSync & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export enum RenderSyncScope {
|
export enum RenderSyncScope {
|
||||||
Service = "service"
|
Service = "service",
|
||||||
|
EnvironmentGroup = "environment-group"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum RenderSyncType {
|
export enum RenderSyncType {
|
||||||
|
|||||||
+54
-13
@@ -1,5 +1,8 @@
|
|||||||
import { useRenderConnectionListServices } from "@app/hooks/api/appConnections/render";
|
import {
|
||||||
import { TRenderSync } from "@app/hooks/api/secretSyncs/types/render-sync";
|
useRenderConnectionListEnvironmentGroups,
|
||||||
|
useRenderConnectionListServices
|
||||||
|
} from "@app/hooks/api/appConnections/render";
|
||||||
|
import { RenderSyncScope, TRenderSync } from "@app/hooks/api/secretSyncs/types/render-sync";
|
||||||
|
|
||||||
import { getSecretSyncDestinationColValues } from "../helpers";
|
import { getSecretSyncDestinationColValues } from "../helpers";
|
||||||
import { SecretSyncTableCell } from "../SecretSyncTableCell";
|
import { SecretSyncTableCell } from "../SecretSyncTableCell";
|
||||||
@@ -9,21 +12,59 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const RenderSyncDestinationCol = ({ secretSync }: Props) => {
|
export const RenderSyncDestinationCol = ({ secretSync }: Props) => {
|
||||||
|
const isServiceScope = secretSync.destinationConfig.scope === RenderSyncScope.Service;
|
||||||
|
|
||||||
const { data: services = [], isPending } = useRenderConnectionListServices(
|
const { data: services = [], isPending } = useRenderConnectionListServices(
|
||||||
secretSync.connectionId
|
secretSync.connectionId,
|
||||||
|
{
|
||||||
|
enabled: isServiceScope
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const { primaryText, secondaryText } = getSecretSyncDestinationColValues({
|
const { data: groups = [], isPending: isGroupsPending } =
|
||||||
...secretSync,
|
useRenderConnectionListEnvironmentGroups(secretSync.connectionId, { enabled: !isServiceScope });
|
||||||
destinationConfig: {
|
|
||||||
...secretSync.destinationConfig,
|
switch (secretSync.destinationConfig.scope) {
|
||||||
serviceName: services.find((s) => s.id === secretSync.destinationConfig.serviceId)?.name
|
case RenderSyncScope.Service: {
|
||||||
|
const id = secretSync.destinationConfig.serviceId;
|
||||||
|
const { primaryText, secondaryText } = getSecretSyncDestinationColValues({
|
||||||
|
...secretSync,
|
||||||
|
destinationConfig: {
|
||||||
|
...secretSync.destinationConfig,
|
||||||
|
serviceName: services.find((s) => s.id === id)?.name
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isPending) {
|
||||||
|
return (
|
||||||
|
<SecretSyncTableCell primaryText="Loading service info..." secondaryText="Service" />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <SecretSyncTableCell primaryText={primaryText} secondaryText={secondaryText} />;
|
||||||
}
|
}
|
||||||
});
|
case RenderSyncScope.EnvironmentGroup: {
|
||||||
|
const id = secretSync.destinationConfig.environmentGroupId;
|
||||||
|
const { primaryText, secondaryText } = getSecretSyncDestinationColValues({
|
||||||
|
...secretSync,
|
||||||
|
destinationConfig: {
|
||||||
|
...secretSync.destinationConfig,
|
||||||
|
environmentGroupName: groups.find((s) => s.id === id)?.name
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (isPending) {
|
if (isGroupsPending) {
|
||||||
return <SecretSyncTableCell primaryText="Loading service info..." secondaryText="Service" />;
|
return (
|
||||||
|
<SecretSyncTableCell
|
||||||
|
primaryText="Loading environment group info..."
|
||||||
|
secondaryText="Environment Group"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <SecretSyncTableCell primaryText={primaryText} secondaryText={secondaryText} />;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error("Unknown render sync destination scope");
|
||||||
}
|
}
|
||||||
|
|
||||||
return <SecretSyncTableCell primaryText={primaryText} secondaryText={secondaryText} />;
|
|
||||||
};
|
};
|
||||||
|
|||||||
+10
-2
@@ -8,6 +8,7 @@ import {
|
|||||||
} from "@app/hooks/api/secretSyncs/types/github-sync";
|
} from "@app/hooks/api/secretSyncs/types/github-sync";
|
||||||
import { GitLabSyncScope } from "@app/hooks/api/secretSyncs/types/gitlab-sync";
|
import { GitLabSyncScope } from "@app/hooks/api/secretSyncs/types/gitlab-sync";
|
||||||
import { HumanitecSyncScope } from "@app/hooks/api/secretSyncs/types/humanitec-sync";
|
import { HumanitecSyncScope } from "@app/hooks/api/secretSyncs/types/humanitec-sync";
|
||||||
|
import { RenderSyncScope } from "@app/hooks/api/secretSyncs/types/render-sync";
|
||||||
|
|
||||||
// This functional ensures parity across what is displayed in the destination column
|
// This functional ensures parity across what is displayed in the destination column
|
||||||
// and the values used when search filtering
|
// and the values used when search filtering
|
||||||
@@ -125,8 +126,15 @@ export const getSecretSyncDestinationColValues = (secretSync: TSecretSync) => {
|
|||||||
secondaryText = destinationConfig.app;
|
secondaryText = destinationConfig.app;
|
||||||
break;
|
break;
|
||||||
case SecretSync.Render:
|
case SecretSync.Render:
|
||||||
primaryText = destinationConfig.serviceName ?? destinationConfig.serviceId;
|
if (destinationConfig.scope === RenderSyncScope.Service) {
|
||||||
secondaryText = "Service";
|
primaryText = destinationConfig.serviceName ?? destinationConfig.serviceId;
|
||||||
|
secondaryText = "Service";
|
||||||
|
} else {
|
||||||
|
primaryText =
|
||||||
|
destinationConfig.environmentGroupName ?? destinationConfig.environmentGroupId;
|
||||||
|
secondaryText = "Environment Group";
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case SecretSync.Flyio:
|
case SecretSync.Flyio:
|
||||||
primaryText = destinationConfig.appId;
|
primaryText = destinationConfig.appId;
|
||||||
|
|||||||
+38
-11
@@ -1,23 +1,50 @@
|
|||||||
import { GenericFieldLabel } from "@app/components/secret-syncs";
|
import { GenericFieldLabel } from "@app/components/secret-syncs";
|
||||||
import { useRenderConnectionListServices } from "@app/hooks/api/appConnections/render";
|
import {
|
||||||
import { TRenderSync } from "@app/hooks/api/secretSyncs/types/render-sync";
|
useRenderConnectionListEnvironmentGroups,
|
||||||
|
useRenderConnectionListServices
|
||||||
|
} from "@app/hooks/api/appConnections/render";
|
||||||
|
import { RenderSyncScope, TRenderSync } from "@app/hooks/api/secretSyncs/types/render-sync";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
secretSync: TRenderSync;
|
secretSync: TRenderSync;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RenderSyncDestinationSection = ({ secretSync }: Props) => {
|
export const RenderSyncDestinationSection = ({ secretSync }: Props) => {
|
||||||
|
const isServiceScope = secretSync.destinationConfig.scope === RenderSyncScope.Service;
|
||||||
|
|
||||||
const { data: services = [], isPending } = useRenderConnectionListServices(
|
const { data: services = [], isPending } = useRenderConnectionListServices(
|
||||||
secretSync.connectionId
|
secretSync.connectionId,
|
||||||
|
{
|
||||||
|
enabled: isServiceScope
|
||||||
|
}
|
||||||
);
|
);
|
||||||
const {
|
|
||||||
destinationConfig: { serviceId }
|
|
||||||
} = secretSync;
|
|
||||||
|
|
||||||
if (isPending) {
|
const { data: groups = [], isPending: isGroupsPending } =
|
||||||
return <GenericFieldLabel label="Service">Loading...</GenericFieldLabel>;
|
useRenderConnectionListEnvironmentGroups(secretSync.connectionId, { enabled: !isServiceScope });
|
||||||
|
|
||||||
|
switch (secretSync.destinationConfig.scope) {
|
||||||
|
case RenderSyncScope.Service: {
|
||||||
|
const id = secretSync.destinationConfig.serviceId;
|
||||||
|
|
||||||
|
if (isPending) {
|
||||||
|
return <GenericFieldLabel label="Service">Loading...</GenericFieldLabel>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const serviceName = services.find((service) => service.id === id)?.name;
|
||||||
|
return <GenericFieldLabel label="Service">{serviceName ?? id}</GenericFieldLabel>;
|
||||||
|
}
|
||||||
|
|
||||||
|
case RenderSyncScope.EnvironmentGroup: {
|
||||||
|
const id = secretSync.destinationConfig.environmentGroupId;
|
||||||
|
|
||||||
|
if (isGroupsPending) {
|
||||||
|
return <GenericFieldLabel label="Environment Group">Loading...</GenericFieldLabel>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const envName = groups.find((g) => g.id === id)?.name;
|
||||||
|
return <GenericFieldLabel label="Environment Group">{envName ?? id}</GenericFieldLabel>;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error("Unknown render sync destination scope");
|
||||||
}
|
}
|
||||||
|
|
||||||
const serviceName = services.find((service) => service.id === serviceId)?.name;
|
|
||||||
return <GenericFieldLabel label="Service">{serviceName ?? serviceId}</GenericFieldLabel>;
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user