mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 21:27:31 +00:00
feat: integrate telemetry service for secret synchronization events and update project role permissions to reflect workspace integrations
This commit is contained in:
@@ -1329,7 +1329,8 @@ export const registerRoutes = async (
|
|||||||
eventBusService,
|
eventBusService,
|
||||||
licenseService,
|
licenseService,
|
||||||
membershipRoleDAL,
|
membershipRoleDAL,
|
||||||
membershipUserDAL
|
membershipUserDAL,
|
||||||
|
telemetryService
|
||||||
});
|
});
|
||||||
|
|
||||||
const projectService = projectServiceFactory({
|
const projectService = projectServiceFactory({
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ import { expandSecretReferencesFactory, getAllSecretReferences } from "../secret
|
|||||||
import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal";
|
import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal";
|
||||||
import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal";
|
import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal";
|
||||||
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
||||||
|
import { TTelemetryServiceFactory } from "../telemetry/telemetry-service";
|
||||||
|
import { PostHogEventTypes } from "../telemetry/telemetry-types";
|
||||||
import { TUserDALFactory } from "../user/user-dal";
|
import { TUserDALFactory } from "../user/user-dal";
|
||||||
import { TWebhookDALFactory } from "../webhook/webhook-dal";
|
import { TWebhookDALFactory } from "../webhook/webhook-dal";
|
||||||
import { fnTriggerWebhook } from "../webhook/webhook-fns";
|
import { fnTriggerWebhook } from "../webhook/webhook-fns";
|
||||||
@@ -120,6 +122,7 @@ type TSecretQueueFactoryDep = {
|
|||||||
reminderService: Pick<TReminderServiceFactory, "createReminderInternal" | "deleteReminderBySecretId">;
|
reminderService: Pick<TReminderServiceFactory, "createReminderInternal" | "deleteReminderBySecretId">;
|
||||||
eventBusService: TEventBusService;
|
eventBusService: TEventBusService;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
|
telemetryService: Pick<TTelemetryServiceFactory, "sendPostHogEvents">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TGetSecrets = {
|
export type TGetSecrets = {
|
||||||
@@ -184,7 +187,8 @@ export const secretQueueFactory = ({
|
|||||||
eventBusService,
|
eventBusService,
|
||||||
licenseService,
|
licenseService,
|
||||||
membershipUserDAL,
|
membershipUserDAL,
|
||||||
membershipRoleDAL
|
membershipRoleDAL,
|
||||||
|
telemetryService
|
||||||
}: TSecretQueueFactoryDep) => {
|
}: TSecretQueueFactoryDep) => {
|
||||||
const integrationMeter = opentelemetry.metrics.getMeter("Integrations");
|
const integrationMeter = opentelemetry.metrics.getMeter("Integrations");
|
||||||
const errorHistogram = integrationMeter.createHistogram("integration_secret_sync_errors", {
|
const errorHistogram = integrationMeter.createHistogram("integration_secret_sync_errors", {
|
||||||
@@ -1029,6 +1033,29 @@ export const secretQueueFactory = ({
|
|||||||
isSynced: response?.isSynced ?? true
|
isSynced: response?.isSynced ?? true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await telemetryService.sendPostHogEvents({
|
||||||
|
event: PostHogEventTypes.IntegrationSynced,
|
||||||
|
distinctId: `project/${projectId}`,
|
||||||
|
organizationId: project.orgId,
|
||||||
|
properties: {
|
||||||
|
integrationId: integration.id,
|
||||||
|
integration: integration.integration,
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
projectId,
|
||||||
|
url: integration.url ?? undefined,
|
||||||
|
app: integration.app ?? undefined,
|
||||||
|
appId: integration.appId ?? undefined,
|
||||||
|
targetEnvironment: integration.targetEnvironment ?? undefined,
|
||||||
|
targetEnvironmentId: integration.targetEnvironmentId ?? undefined,
|
||||||
|
targetService: integration.targetService ?? undefined,
|
||||||
|
targetServiceId: integration.targetServiceId ?? undefined,
|
||||||
|
path: integration.path ?? undefined,
|
||||||
|
region: integration.region ?? undefined,
|
||||||
|
isManualSync: isManual ?? false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// May be undefined, if it's undefined we assume the sync was successful, hence the strict equality type check.
|
// May be undefined, if it's undefined we assume the sync was successful, hence the strict equality type check.
|
||||||
if (response?.isSynced === false) {
|
if (response?.isSynced === false) {
|
||||||
integrationsFailedToSync.push({
|
integrationsFailedToSync.push({
|
||||||
|
|||||||
@@ -574,7 +574,6 @@
|
|||||||
"pages": [
|
"pages": [
|
||||||
"integrations/cicd/aws-amplify",
|
"integrations/cicd/aws-amplify",
|
||||||
"integrations/cicd/bitbucket",
|
"integrations/cicd/bitbucket",
|
||||||
"integrations/cicd/checkly",
|
|
||||||
"integrations/cicd/githubactions",
|
"integrations/cicd/githubactions",
|
||||||
"integrations/cicd/gitlab",
|
"integrations/cicd/gitlab",
|
||||||
"integrations/cicd/jenkins"
|
"integrations/cicd/jenkins"
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Checkly"
|
|
||||||
description: "How to sync secrets from Infisical to Checkly"
|
|
||||||
---
|
|
||||||
|
|
||||||
Prerequisites:
|
|
||||||
|
|
||||||
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
|
|
||||||
|
|
||||||
<Steps>
|
|
||||||
<Step title="Authorize Infisical for Checkly">
|
|
||||||
Obtain a Checkly API Key in User Settings > API Keys.
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
Navigate to your project's integrations tab in Infisical.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Press on the Checkly tile and input your Checkly API Key to grant Infisical access to your Checkly account.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</Step>
|
|
||||||
<Step title="Start integration">
|
|
||||||
Select which Infisical environment secrets you want to sync to Checkly and press create integration to start syncing secrets.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
<Note>
|
|
||||||
Infisical integrates with Checkly's environment variables at the **global** and **group** levels.
|
|
||||||
|
|
||||||
To sync secrets to a specific group, you can select a group from the Checkly Group dropdown; otherwise, leaving it empty will sync secrets globally.
|
|
||||||
</Note>
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
<Info>
|
|
||||||
In the new version of the Checkly integration, you are able to specify suffixes that depend on the secrets' environment and path.
|
|
||||||
If you choose to do so, you should utilize such suffixes for ALL Checkly integrations – otherwise the integration system
|
|
||||||
might run into issues with deleting secrets from the wrong environments.
|
|
||||||
</Info>
|
|
||||||
</Step>
|
|
||||||
</Steps>
|
|
||||||
+12
-1
@@ -18,7 +18,8 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
Tr
|
Tr
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { ProjectPermissionSub } from "@app/context";
|
import { ProjectPermissionSub, useProject } from "@app/context";
|
||||||
|
import { useGetWorkspaceIntegrations } from "@app/hooks/api";
|
||||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -46,6 +47,9 @@ type TForm = { permissions: Record<ProjectPermissionSub, boolean> };
|
|||||||
const Content = ({ onClose, type: projectType }: ContentProps) => {
|
const Content = ({ onClose, type: projectType }: ContentProps) => {
|
||||||
const rootForm = useFormContext<TFormSchema>();
|
const rootForm = useFormContext<TFormSchema>();
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
|
const { currentProject } = useProject();
|
||||||
|
const { data: integrations = [] } = useGetWorkspaceIntegrations(currentProject?.id ?? "");
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
@@ -60,6 +64,8 @@ const Content = ({ onClose, type: projectType }: ContentProps) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const hasNativeIntegrations = integrations.length > 0;
|
||||||
|
|
||||||
const filteredPolicies = Object.entries(PROJECT_PERMISSION_OBJECT)
|
const filteredPolicies = Object.entries(PROJECT_PERMISSION_OBJECT)
|
||||||
.filter(
|
.filter(
|
||||||
([subject, { title }]) =>
|
([subject, { title }]) =>
|
||||||
@@ -68,6 +74,11 @@ const Content = ({ onClose, type: projectType }: ContentProps) => {
|
|||||||
] && (search ? title.toLowerCase().includes(search.toLowerCase()) : true)
|
] && (search ? title.toLowerCase().includes(search.toLowerCase()) : true)
|
||||||
)
|
)
|
||||||
.filter(([subject]) => !EXCLUDED_PERMISSION_SUBS.includes(subject as ProjectPermissionSub))
|
.filter(([subject]) => !EXCLUDED_PERMISSION_SUBS.includes(subject as ProjectPermissionSub))
|
||||||
|
.filter(
|
||||||
|
([subject]) =>
|
||||||
|
// Hide Native Integrations policy if project has no integrations
|
||||||
|
subject !== ProjectPermissionSub.Integrations || hasNativeIntegrations
|
||||||
|
)
|
||||||
.sort((a, b) => a[1].title.localeCompare(b[1].title))
|
.sort((a, b) => a[1].title.localeCompare(b[1].title))
|
||||||
.map(([subject]) => subject);
|
.map(([subject]) => subject);
|
||||||
|
|
||||||
|
|||||||
+12
-1
@@ -11,7 +11,11 @@ import { Button } from "@app/components/v2";
|
|||||||
import { ProjectPermissionSub, useProject } from "@app/context";
|
import { ProjectPermissionSub, useProject } from "@app/context";
|
||||||
import { ProjectPermissionSet } from "@app/context/ProjectPermissionContext";
|
import { ProjectPermissionSet } from "@app/context/ProjectPermissionContext";
|
||||||
import { evaluatePermissionsAbility } from "@app/helpers/permissions";
|
import { evaluatePermissionsAbility } from "@app/helpers/permissions";
|
||||||
import { useGetProjectRoleBySlug, useUpdateProjectRole } from "@app/hooks/api";
|
import {
|
||||||
|
useGetProjectRoleBySlug,
|
||||||
|
useGetWorkspaceIntegrations,
|
||||||
|
useUpdateProjectRole
|
||||||
|
} from "@app/hooks/api";
|
||||||
import { ProjectType } from "@app/hooks/api/projects/types";
|
import { ProjectType } from "@app/hooks/api/projects/types";
|
||||||
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
|
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
|
||||||
|
|
||||||
@@ -105,6 +109,8 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => {
|
|||||||
currentProject?.id ?? "",
|
currentProject?.id ?? "",
|
||||||
roleSlug as string
|
roleSlug as string
|
||||||
);
|
);
|
||||||
|
const { data: integrations = [] } = useGetWorkspaceIntegrations(projectId);
|
||||||
|
const hasNativeIntegrations = integrations.length > 0;
|
||||||
|
|
||||||
const [showAccessTree, setShowAccessTree] = useState<ProjectPermissionSub | null>(null);
|
const [showAccessTree, setShowAccessTree] = useState<ProjectPermissionSub | null>(null);
|
||||||
|
|
||||||
@@ -198,6 +204,11 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => {
|
|||||||
{!isPending && <PermissionEmptyState />}
|
{!isPending && <PermissionEmptyState />}
|
||||||
{(Object.keys(PROJECT_PERMISSION_OBJECT) as ProjectPermissionSub[])
|
{(Object.keys(PROJECT_PERMISSION_OBJECT) as ProjectPermissionSub[])
|
||||||
.filter((subject) => !EXCLUDED_PERMISSION_SUBS.includes(subject))
|
.filter((subject) => !EXCLUDED_PERMISSION_SUBS.includes(subject))
|
||||||
|
.filter(
|
||||||
|
(subject) =>
|
||||||
|
// Hide Native Integrations policy if project has no integrations
|
||||||
|
subject !== ProjectPermissionSub.Integrations || hasNativeIntegrations
|
||||||
|
)
|
||||||
.map((subject) => (
|
.map((subject) => (
|
||||||
<GeneralPermissionPolicies
|
<GeneralPermissionPolicies
|
||||||
subject={subject}
|
subject={subject}
|
||||||
|
|||||||
@@ -1,17 +1,11 @@
|
|||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { faWarning } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { useNavigate, useSearch } from "@tanstack/react-router";
|
import { useNavigate, useSearch } from "@tanstack/react-router";
|
||||||
|
|
||||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||||
import {
|
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
|
||||||
Alert,
|
|
||||||
AlertDescription,
|
|
||||||
PageHeader,
|
|
||||||
Tab,
|
|
||||||
TabList,
|
|
||||||
TabPanel,
|
|
||||||
Tabs
|
|
||||||
} from "@app/components/v2";
|
|
||||||
import { ROUTE_PATHS } from "@app/const/routes";
|
import { ROUTE_PATHS } from "@app/const/routes";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
@@ -106,28 +100,33 @@ export const IntegrationsListPage = () => {
|
|||||||
</TabPanel>
|
</TabPanel>
|
||||||
{hasNativeIntegrations && (
|
{hasNativeIntegrations && (
|
||||||
<TabPanel value={IntegrationsListPageTabs.NativeIntegrations}>
|
<TabPanel value={IntegrationsListPageTabs.NativeIntegrations}>
|
||||||
<Alert variant="warning" className="mb-4" hideTitle>
|
<div className="mb-4 flex items-start rounded-md border border-yellow-600 bg-yellow-900/20 px-3 py-2">
|
||||||
<AlertDescription>
|
<div className="flex text-sm text-yellow-100">
|
||||||
We're moving Native Integrations to{" "}
|
<FontAwesomeIcon icon={faWarning} className="mt-1 mr-2 text-yellow-600" />
|
||||||
<a
|
<div>
|
||||||
href="https://infisical.com/docs/integrations/secret-syncs/overview"
|
<p>
|
||||||
target="_blank"
|
We're moving Native Integrations to{" "}
|
||||||
rel="noopener noreferrer"
|
<a
|
||||||
className="underline underline-offset-2 hover:text-mineshaft-100"
|
href="https://infisical.com/docs/integrations/secret-syncs/overview"
|
||||||
>
|
target="_blank"
|
||||||
Secret Syncs
|
rel="noopener noreferrer"
|
||||||
</a>
|
className="underline underline-offset-2 hover:text-mineshaft-100"
|
||||||
. If the integration you need isn't available in the Secret Syncs menu,
|
>
|
||||||
please get in touch with us at{" "}
|
Secret Syncs
|
||||||
<a
|
</a>
|
||||||
href="mailto:[email protected]"
|
. If the integration you need isn't available in the Secret Syncs menu,
|
||||||
className="underline underline-offset-2 hover:text-mineshaft-100"
|
please get in touch with us at{" "}
|
||||||
>
|
<a
|
||||||
team@infisical.com
|
href="mailto:team@infisical.com"
|
||||||
</a>
|
className="underline underline-offset-2 hover:text-mineshaft-100"
|
||||||
.
|
>
|
||||||
</AlertDescription>
|
team@infisical.com
|
||||||
</Alert>
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
renderGuardBanner
|
renderGuardBanner
|
||||||
I={ProjectPermissionActions.Read}
|
I={ProjectPermissionActions.Read}
|
||||||
|
|||||||
Reference in New Issue
Block a user