diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/AppSidebarPopoverSystem.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/AppSidebarPopoverSystem.tsx index 43487aff..1ba753cb 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/AppSidebarPopoverSystem.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/AppSidebarPopoverSystem.tsx @@ -57,17 +57,15 @@ export const AppSidebarSystemPopover: React.FC<{ - {policyId != null && ( - - setActiveSidebarItem(null)} - className="flex items-center gap-1 " - > - - - - )} + + setActiveSidebarItem(null)} + className="flex items-center gap-1 " + > + + + ))} diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/deployment-resource-drawer/DeploymentResourceDrawer.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/deployment-resource-drawer/DeploymentResourceDrawer.tsx index ce12fd30..3ca41c90 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/deployment-resource-drawer/DeploymentResourceDrawer.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/deployment-resource-drawer/DeploymentResourceDrawer.tsx @@ -48,10 +48,10 @@ export const DeploymentResourceDrawer: React.FC = () => { const releaseFilter = environment?.releaseChannels.find((rc) => rc.deploymentId === deploymentId) - ?.filter ?? - environment?.policy?.releaseChannels.find( + ?.releaseFilter ?? + environment?.policy.releaseChannels.find( (rc) => rc.deploymentId === deploymentId, - )?.filter ?? + )?.releaseFilter ?? undefined; const jobFilter: JobCondition = { diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-drawer/EnvironmentDrawer.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-drawer/EnvironmentDrawer.tsx index fed4d955..d36cf262 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-drawer/EnvironmentDrawer.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-drawer/EnvironmentDrawer.tsx @@ -3,11 +3,15 @@ import React from "react"; import { useParams, useRouter, useSearchParams } from "next/navigation"; import { + IconCalendar, + IconChecklist, + IconDeviceRemote, IconDotsVertical, - IconFilter, IconInfoCircle, IconLoader2, + IconLock, IconPlant, + IconRocket, IconTarget, } from "@tabler/icons-react"; @@ -19,13 +23,8 @@ import { TabButton } from "../TabButton"; import { EnvironmentDropdownMenu } from "./EnvironmentDropdownMenu"; import { EditFilterForm } from "./Filter"; import { Overview } from "./Overview"; -import { ReleaseChannels } from "./ReleaseChannels"; - -export enum EnvironmentDrawerTab { - Overview = "overview", - Resources = "resources", - ReleaseChannels = "release-channels", -} +import { UpdateOverridePolicy } from "./policy-override/UpdateOverride"; +import { EnvironmentDrawerTab } from "./tabs"; const tabParam = "tab"; const useEnvironmentDrawerTab = () => { @@ -44,7 +43,9 @@ const useEnvironmentDrawerTab = () => { router.replace(`${url.pathname}?${url.searchParams.toString()}`); }; - return { tab, setTab }; + const typedTab = tab != null ? (tab as EnvironmentDrawerTab) : null; + + return { tab: typedTab, setTab }; }; const param = "environment_id"; @@ -97,6 +98,8 @@ export const EnvironmentDrawer: React.FC = () => { const loading = environmentQ.isLoading || workspaceQ.isLoading || deploymentsQ.isLoading; + const isUsingOverridePolicy = environment?.policy.isOverride ?? false; + return ( { {!loading && (
-
- setTab(EnvironmentDrawerTab.Overview)} - icon={} - label="Overview" - /> - setTab(EnvironmentDrawerTab.Resources)} - icon={} - label="Resources" - /> - setTab(EnvironmentDrawerTab.ReleaseChannels)} - icon={} - label="Release Channels" - /> -
+ {isUsingOverridePolicy && ( +
+
+

General

+ setTab(EnvironmentDrawerTab.Overview)} + icon={} + label="Overview" + /> + setTab(EnvironmentDrawerTab.Resources)} + icon={} + label="Resources" + /> +
+ +
+

Policy Settings

+ setTab(EnvironmentDrawerTab.Approval)} + icon={} + label="Approval & Governance" + /> + setTab(EnvironmentDrawerTab.Concurrency)} + icon={} + label="Deployment Control" + /> + setTab(EnvironmentDrawerTab.Management)} + icon={} + label="Release Management" + /> + setTab(EnvironmentDrawerTab.ReleaseChannels)} + icon={} + label="Release Channels" + /> + setTab(EnvironmentDrawerTab.Rollout)} + icon={} + label="Rollout and Timing" + /> +
+
+ )} + + {!isUsingOverridePolicy && ( +
+ setTab(EnvironmentDrawerTab.Overview)} + icon={} + label="Overview" + /> + setTab(EnvironmentDrawerTab.Resources)} + icon={} + label="Resources" + /> +
+ )} {environment != null && (
@@ -165,13 +220,14 @@ export const EnvironmentDrawer: React.FC = () => { workspaceId={workspace.id} /> )} - {tab === EnvironmentDrawerTab.ReleaseChannels && - deployments != null && ( - - )} + {environment.policy.isOverride && ( + + )}
)}
diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-drawer/policy-override/UpdateOverride.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-drawer/policy-override/UpdateOverride.tsx new file mode 100644 index 00000000..64175929 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-drawer/policy-override/UpdateOverride.tsx @@ -0,0 +1,78 @@ +import type { RouterOutputs } from "@ctrlplane/api"; +import type * as SCHEMA from "@ctrlplane/db/schema"; + +import { ApprovalAndGovernance } from "../../policy-form-components/ApprovalAndGovernance"; +import { DeploymentControl } from "../../policy-form-components/DeploymentControl"; +import { ReleaseChannels } from "../../policy-form-components/ReleaseChannels"; +import { ReleaseManagement } from "../../policy-form-components/ReleaseManagement"; +import { RolloutAndTiming } from "../../policy-form-components/RolloutAndTiming"; +import { EnvironmentDrawerTab } from "../tabs"; +import { useUpdateOverridePolicy } from "./useOverridePolicy"; + +type Deployment = SCHEMA.Deployment & { + releaseChannels: SCHEMA.ReleaseChannel[]; +}; + +type Policy = NonNullable< + NonNullable["policy"] +>; + +type UpdateOverridePolicyProps = { + environment: SCHEMA.Environment; + environmentPolicy: Policy; + activeTab: EnvironmentDrawerTab | null; + deployments: Deployment[]; +}; + +export const UpdateOverridePolicy: React.FC = ({ + environment, + environmentPolicy, + activeTab, + deployments, +}) => { + const { onUpdate, isUpdating } = useUpdateOverridePolicy( + environment, + environmentPolicy, + ); + + return ( + <> + {activeTab === EnvironmentDrawerTab.Approval && ( + + )} + {activeTab === EnvironmentDrawerTab.Concurrency && ( + + )} + {activeTab === EnvironmentDrawerTab.Management && ( + + )} + {activeTab === EnvironmentDrawerTab.ReleaseChannels && ( + + )} + {activeTab === EnvironmentDrawerTab.Rollout && ( + + )} + + ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-drawer/policy-override/useOverridePolicy.ts b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-drawer/policy-override/useOverridePolicy.ts new file mode 100644 index 00000000..8f9805a7 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-drawer/policy-override/useOverridePolicy.ts @@ -0,0 +1,42 @@ +import type * as SCHEMA from "@ctrlplane/db/schema"; + +import { api } from "~/trpc/react"; + +const useInvalidateQueries = ( + environmentId: string, + systemId: string, + policyId?: string, +) => { + const utils = api.useUtils(); + const invalidateQueries = () => { + utils.environment.policy.byId.invalidate(policyId); + utils.environment.policy.bySystemId.invalidate(systemId); + utils.environment.byId.invalidate(environmentId); + }; + return invalidateQueries; +}; + +export const useUpdateOverridePolicy = ( + environment: SCHEMA.Environment, + policy: SCHEMA.EnvironmentPolicy, +) => { + const updatePolicy = api.environment.policy.update.useMutation(); + const invalidateQueries = useInvalidateQueries( + environment.id, + environment.systemId, + policy.id, + ); + + const onUpdate = (data: SCHEMA.UpdateEnvironmentPolicy) => + updatePolicy + .mutateAsync({ + id: policy.id, + data, + }) + .then(invalidateQueries); + + return { + onUpdate, + isUpdating: updatePolicy.isPending, + }; +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-drawer/tabs.ts b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-drawer/tabs.ts new file mode 100644 index 00000000..ca752e56 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-drawer/tabs.ts @@ -0,0 +1,9 @@ +export enum EnvironmentDrawerTab { + Overview = "overview", + Resources = "resources", + Approval = "approval", + Concurrency = "concurrency", + Management = "management", + ReleaseChannels = "release-channels", + Rollout = "rollout", +} diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/ApprovalAndGovernance.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/ApprovalAndGovernance.tsx deleted file mode 100644 index 02655407..00000000 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/ApprovalAndGovernance.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import type * as SCHEMA from "@ctrlplane/db/schema"; -import React from "react"; -import { IconLoader2 } from "@tabler/icons-react"; - -import { Checkbox } from "@ctrlplane/ui/checkbox"; -import { Input } from "@ctrlplane/ui/input"; -import { Label } from "@ctrlplane/ui/label"; -import { RadioGroup, RadioGroupItem } from "@ctrlplane/ui/radio-group"; - -import { api } from "~/trpc/react"; -import { useInvalidatePolicy } from "./useInvalidatePolicy"; - -type ApprovalAndGovernanceProps = { - environmentPolicy: SCHEMA.EnvironmentPolicy; - isLoading: boolean; -}; - -export const ApprovalAndGovernance: React.FC = ({ - environmentPolicy, - isLoading, -}) => { - const updatePolicy = api.environment.policy.update.useMutation(); - const invalidatePolicy = useInvalidatePolicy(environmentPolicy); - const { id } = environmentPolicy; - - return ( -
-
-

- Approval & Governance - {(isLoading || updatePolicy.isPending) && ( - - )} -

- - This category defines policies that govern the oversight and approval - process for deployments. These policies ensure that deployments meet - specific criteria or gain necessary approvals before proceeding, - contributing to compliance, quality assurance, and overall governance - of the deployment process. - -
- -
-
-

Approval gates

- - If enabled, a release will require approval from an authorized user - before it can be deployed to any environment with this policy. - -
- -
- { - updatePolicy - .mutateAsync({ - id, - data: { approvalRequirement: value ? "manual" : "automatic" }, - }) - .then(invalidatePolicy); - }} - /> - -
-
- -
-
-

Previous Deploy Status

- - Specify a minimum number of resources in dependent environments to - successfully be deployed to before triggering a release. For - example, specifying that all resources in QA must be deployed to - before releasing to PROD. - -
- -
- - updatePolicy - .mutateAsync({ id, data: { successType: value } }) - .then(invalidatePolicy) - } - > -
- - -
- -
- - -
- -
- - -
-
-
-
-
- ); -}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/EnvironmentPolicyDrawer.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/EnvironmentPolicyDrawer.tsx index 58096fc9..a2ea7d67 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/EnvironmentPolicyDrawer.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/EnvironmentPolicyDrawer.tsx @@ -24,14 +24,15 @@ import { } from "@ctrlplane/ui/dropdown-menu"; import { api } from "~/trpc/react"; +import { ApprovalAndGovernance } from "../policy-form-components/ApprovalAndGovernance"; +import { DeploymentControl } from "../policy-form-components/DeploymentControl"; +import { ReleaseChannels } from "../policy-form-components/ReleaseChannels"; +import { ReleaseManagement } from "../policy-form-components/ReleaseManagement"; +import { RolloutAndTiming } from "../policy-form-components/RolloutAndTiming"; import { TabButton } from "../TabButton"; -import { ApprovalAndGovernance } from "./ApprovalAndGovernance"; -import { DeploymentControl } from "./DeploymentControl"; import { Overview } from "./Overview"; import { DeleteEnvironmentPolicyDialog } from "./PolicyDeleteDialog"; -import { ReleaseChannels } from "./ReleaseChannels"; -import { ReleaseManagement } from "./ReleaseManagement"; -import { RolloutAndTiming } from "./RolloutAndTiming"; +import { useUpdatePolicy } from "./useUpdatePolicy"; export enum EnvironmentPolicyDrawerTab { Overview = "overview", @@ -92,32 +93,6 @@ export const useEnvironmentPolicyDrawer = () => { }; }; -type Deployment = SCHEMA.Deployment & { - releaseChannels: SCHEMA.ReleaseChannel[]; -}; - -type ViewProps = { - activeTab: EnvironmentPolicyDrawerTab; - environmentPolicy: SCHEMA.EnvironmentPolicy & { - releaseWindows: SCHEMA.EnvironmentPolicyReleaseWindow[]; - releaseChannels: SCHEMA.ReleaseChannel[]; - }; - deployments: Deployment[]; - isLoading: boolean; -}; - -const View: React.FC = (props) => - ({ - [EnvironmentPolicyDrawerTab.Overview]: , - [EnvironmentPolicyDrawerTab.Approval]: , - [EnvironmentPolicyDrawerTab.Concurrency]: , - [EnvironmentPolicyDrawerTab.Management]: , - [EnvironmentPolicyDrawerTab.Rollout]: , - [EnvironmentPolicyDrawerTab.ReleaseChannels]: ( - - ), - })[props.activeTab]; - const PolicyDropdownMenu: React.FC<{ environmentPolicy: SCHEMA.EnvironmentPolicy; children: React.ReactNode; @@ -138,6 +113,68 @@ const PolicyDropdownMenu: React.FC<{ ); +export type ViewProps = { + environmentPolicy: SCHEMA.EnvironmentPolicy & { + releaseWindows: SCHEMA.EnvironmentPolicyReleaseWindow[]; + releaseChannels: SCHEMA.ReleaseChannel[]; + }; + activeTab: EnvironmentPolicyDrawerTab; +}; + +export const View: React.FC = ({ environmentPolicy, activeTab }) => { + const deploymentsQ = api.deployment.bySystemId.useQuery( + environmentPolicy.systemId, + ); + const { data: deployments, isLoading: isDeploymentsLoading } = deploymentsQ; + + const { onUpdate, isUpdating } = useUpdatePolicy(environmentPolicy); + const isLoading = isDeploymentsLoading || isUpdating; + + return ( +
+ {activeTab === EnvironmentPolicyDrawerTab.Overview && ( + + )} + {activeTab === EnvironmentPolicyDrawerTab.Approval && ( + + )} + {activeTab === EnvironmentPolicyDrawerTab.Concurrency && ( + + )} + {activeTab === EnvironmentPolicyDrawerTab.Management && ( + + )} + {activeTab === EnvironmentPolicyDrawerTab.ReleaseChannels && ( + + )} + {activeTab === EnvironmentPolicyDrawerTab.Rollout && ( + + )} +
+ ); +}; + export const EnvironmentPolicyDrawer: React.FC = () => { const { environmentPolicyId, removeEnvironmentPolicyId, tab, setTab } = useEnvironmentPolicyDrawer(); @@ -147,13 +184,9 @@ export const EnvironmentPolicyDrawer: React.FC = () => { environmentPolicyId ?? "", { enabled: isOpen }, ); - const { data: environmentPolicy, isLoading } = environmentPolicyQ; + const { data: environmentPolicy } = environmentPolicyQ; - const deploymentsQ = api.deployment.bySystemId.useQuery( - environmentPolicy?.systemId ?? "", - { enabled: isOpen && environmentPolicy != null }, - ); - const deployments = deploymentsQ.data; + const activeTab = tab ?? EnvironmentPolicyDrawerTab.Overview; return ( @@ -222,14 +255,7 @@ export const EnvironmentPolicyDrawer: React.FC = () => { {environmentPolicy != null && ( -
- -
+ )}
diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/useInvalidatePolicy.ts b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/useUpdatePolicy.ts similarity index 50% rename from apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/useInvalidatePolicy.ts rename to apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/useUpdatePolicy.ts index 49fa82f6..01e19317 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/useInvalidatePolicy.ts +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/useUpdatePolicy.ts @@ -2,13 +2,22 @@ import type * as SCHEMA from "@ctrlplane/db/schema"; import { api } from "~/trpc/react"; -export const useInvalidatePolicy = ( +export const useUpdatePolicy = ( environmentPolicy: SCHEMA.EnvironmentPolicy, ) => { + const updatePolicy = api.environment.policy.update.useMutation(); const utils = api.useUtils(); const { id, systemId } = environmentPolicy; - return () => { + const invalidatePolicy = () => { utils.environment.policy.byId.invalidate(id); utils.environment.policy.bySystemId.invalidate(systemId); }; + + const onUpdate = (data: SCHEMA.UpdateEnvironmentPolicy) => + updatePolicy.mutateAsync({ id, data }).then(invalidatePolicy); + + return { + onUpdate, + isUpdating: updatePolicy.isPending, + }; }; diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/policy-form-components/ApprovalAndGovernance.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/policy-form-components/ApprovalAndGovernance.tsx new file mode 100644 index 00000000..46e50fd1 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/policy-form-components/ApprovalAndGovernance.tsx @@ -0,0 +1,115 @@ +import type * as SCHEMA from "@ctrlplane/db/schema"; +import React from "react"; +import { IconLoader2 } from "@tabler/icons-react"; + +import { Checkbox } from "@ctrlplane/ui/checkbox"; +import { Input } from "@ctrlplane/ui/input"; +import { Label } from "@ctrlplane/ui/label"; +import { RadioGroup, RadioGroupItem } from "@ctrlplane/ui/radio-group"; + +type ApprovalAndGovernanceProps = { + environmentPolicy: { + approvalRequirement: "manual" | "automatic"; + successType: "all" | "some" | "optional"; + successMinimum: number; + }; + onUpdate: (data: SCHEMA.UpdateEnvironmentPolicy) => Promise; + isLoading: boolean; +}; + +export const ApprovalAndGovernance: React.FC = ({ + environmentPolicy, + onUpdate, + isLoading, +}) => ( +
+
+

+ Approval & Governance + {isLoading && } +

+ + This category defines policies that govern the oversight and approval + process for deployments. These policies ensure that deployments meet + specific criteria or gain necessary approvals before proceeding, + contributing to compliance, quality assurance, and overall governance of + the deployment process. + +
+ +
+
+

Approval gates

+ + If enabled, a release will require approval from an authorized user + before it can be deployed to any environment with this policy. + +
+ +
+ + onUpdate({ approvalRequirement: value ? "manual" : "automatic" }) + } + /> + +
+
+ +
+
+

Previous Deploy Status

+ + Specify a minimum number of resources in dependent environments to + successfully be deployed to before triggering a release. For example, + specifying that all resources in QA must be deployed to before + releasing to PROD. + +
+ +
+ + onUpdate({ successType: value }) + } + > +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+
+
+); diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/DeploymentControl.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/policy-form-components/DeploymentControl.tsx similarity index 77% rename from apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/DeploymentControl.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/_components/policy-form-components/DeploymentControl.tsx index 46ca0e75..7a6890e5 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/DeploymentControl.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/policy-form-components/DeploymentControl.tsx @@ -8,34 +8,31 @@ import { Label } from "@ctrlplane/ui/label"; import { RadioGroup, RadioGroupItem } from "@ctrlplane/ui/radio-group"; import { toast } from "@ctrlplane/ui/toast"; -import { api } from "~/trpc/react"; -import { useInvalidatePolicy } from "./useInvalidatePolicy"; - type DeploymentControlProps = { - environmentPolicy: SCHEMA.EnvironmentPolicy; + environmentPolicy: { concurrencyLimit: number | null }; + onUpdate: (data: SCHEMA.UpdateEnvironmentPolicy) => Promise; isLoading: boolean; }; export const DeploymentControl: React.FC = ({ environmentPolicy, + onUpdate, isLoading, }) => { const [concurrencyLimit, setConcurrencyLimit] = useState( environmentPolicy.concurrencyLimit?.toString() ?? "", ); - const updatePolicy = api.environment.policy.update.useMutation(); - const invalidatePolicy = useInvalidatePolicy(environmentPolicy); - const { id } = environmentPolicy; useDebounce( () => { - if (concurrencyLimit === "") return; - const limit = Number(concurrencyLimit); - if (Number.isNaN(limit)) return; - updatePolicy - .mutateAsync({ id, data: { concurrencyLimit: limit } }) - .then(invalidatePolicy) - .catch((e) => toast.error(e.message)); + try { + if (concurrencyLimit === "") return; + const limit = Number(concurrencyLimit); + if (Number.isNaN(limit)) return; + onUpdate({ concurrencyLimit: limit }); + } catch { + toast.error("Failed to update concurrency limit"); + } }, 300, [concurrencyLimit], @@ -46,9 +43,7 @@ export const DeploymentControl: React.FC = ({

Deployment Control - {(isLoading || updatePolicy.isPending) && ( - - )} + {isLoading && }

Deployment control policies focus on regulating how deployments are @@ -71,9 +66,7 @@ export const DeploymentControl: React.FC = ({ onValueChange={(value) => { const concurrencyLimit = value === "some" ? 1 : null; setConcurrencyLimit(String(concurrencyLimit ?? "")); - updatePolicy - .mutateAsync({ id, data: { concurrencyLimit } }) - .then(invalidatePolicy); + onUpdate({ concurrencyLimit }); }} >
diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/ReleaseChannels.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/policy-form-components/ReleaseChannels.tsx similarity index 82% rename from apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/ReleaseChannels.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/_components/policy-form-components/ReleaseChannels.tsx index e0fc8ac3..65bb2f78 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/ReleaseChannels.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/policy-form-components/ReleaseChannels.tsx @@ -11,21 +11,17 @@ import { SelectValue, } from "@ctrlplane/ui/select"; -import { api } from "~/trpc/react"; -import { useInvalidatePolicy } from "./useInvalidatePolicy"; - -type Policy = SCHEMA.EnvironmentPolicy & { - releaseChannels: SCHEMA.ReleaseChannel[]; -}; - type Deployment = SCHEMA.Deployment & { releaseChannels: SCHEMA.ReleaseChannel[]; }; +type Policy = { releaseChannels: SCHEMA.ReleaseChannel[] }; + type ReleaseChannelProps = { environmentPolicy: Policy; deployments: Deployment[]; isLoading: boolean; + onUpdate: (data: SCHEMA.UpdateEnvironmentPolicy) => Promise; }; type DeploymentSelectProps = { @@ -34,7 +30,7 @@ type DeploymentSelectProps = { updateReleaseChannel: ( deploymentId: string, channelId: string | null, - ) => void; + ) => Promise; }; const DeploymentSelect: React.FC = ({ @@ -95,16 +91,13 @@ export const ReleaseChannels: React.FC = ({ environmentPolicy, deployments, isLoading, + onUpdate, }) => { - const updatePolicy = api.environment.policy.update.useMutation(); - const invalidatePolicy = useInvalidatePolicy(environmentPolicy); - const deploymentsWithReleaseChannels = deployments.filter( (d) => d.releaseChannels.length > 0, ); - const { id, releaseChannels } = environmentPolicy; - + const { releaseChannels } = environmentPolicy; const currReleaseChannels = Object.fromEntries( deploymentsWithReleaseChannels.map((d) => [ d.id, @@ -115,23 +108,16 @@ export const ReleaseChannels: React.FC = ({ const updateReleaseChannel = ( deploymentId: string, channelId: string | null, - ) => { - const newReleaseChannels = { - ...currReleaseChannels, - [deploymentId]: channelId, - }; - updatePolicy - .mutateAsync({ id, data: { releaseChannels: newReleaseChannels } }) - .then(invalidatePolicy); - }; + ) => + onUpdate({ + releaseChannels: { ...currReleaseChannels, [deploymentId]: channelId }, + }); return (

Release Channels{" "} - {(isLoading || updatePolicy.isPending) && ( - - )} + {isLoading && }

diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/ReleaseManagement.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/policy-form-components/ReleaseManagement.tsx similarity index 76% rename from apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/ReleaseManagement.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/_components/policy-form-components/ReleaseManagement.tsx index 6cf127d6..a3790df4 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/ReleaseManagement.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/policy-form-components/ReleaseManagement.tsx @@ -4,30 +4,25 @@ import { IconLoader2 } from "@tabler/icons-react"; import { Label } from "@ctrlplane/ui/label"; import { RadioGroup, RadioGroupItem } from "@ctrlplane/ui/radio-group"; -import { api } from "~/trpc/react"; -import { useInvalidatePolicy } from "./useInvalidatePolicy"; - type ReleaseManagementProps = { - environmentPolicy: SCHEMA.EnvironmentPolicy; + environmentPolicy: { releaseSequencing: "wait" | "cancel" }; + onUpdate: (data: SCHEMA.UpdateEnvironmentPolicy) => Promise; isLoading: boolean; }; export const ReleaseManagement: React.FC = ({ environmentPolicy, + onUpdate, isLoading, }) => { - const updatePolicy = api.environment.policy.update.useMutation(); - const invalidatePolicy = useInvalidatePolicy(environmentPolicy); - const { releaseSequencing, id } = environmentPolicy; + const { releaseSequencing } = environmentPolicy; return (

Release Management - {(isLoading || updatePolicy.isPending) && ( - - )} + {isLoading && }

Release management policies are concerned with how new and pending @@ -48,10 +43,8 @@ export const ReleaseManagement: React.FC = ({
- updatePolicy - .mutateAsync({ id, data: { releaseSequencing: value } }) - .then(invalidatePolicy) + onValueChange={(releaseSequencing: "wait" | "cancel") => + onUpdate({ releaseSequencing }) } >
diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/RolloutAndTiming.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/policy-form-components/RolloutAndTiming.tsx similarity index 92% rename from apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/RolloutAndTiming.tsx rename to apps/webservice/src/app/[workspaceSlug]/(app)/_components/policy-form-components/RolloutAndTiming.tsx index d18820ad..b878aec2 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/RolloutAndTiming.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/policy-form-components/RolloutAndTiming.tsx @@ -29,10 +29,6 @@ import { SelectTrigger, SelectValue, } from "@ctrlplane/ui/select"; -import { toast } from "@ctrlplane/ui/toast"; - -import { api } from "~/trpc/react"; -import { useInvalidatePolicy } from "./useInvalidatePolicy"; const isValidDuration = (str: string) => { try { @@ -46,7 +42,6 @@ const isValidDuration = (str: string) => { const schema = z.object({ releaseWindows: z.array( z.object({ - policyId: z.string().uuid(), recurrence: z.enum(["hourly", "daily", "weekly", "monthly"]), startTime: z.date(), endTime: z.date(), @@ -61,15 +56,19 @@ const schema = z.object({ }); type RolloutAndTimingProps = { - environmentPolicy: SCHEMA.EnvironmentPolicy & { + environmentPolicy: { + rolloutDuration: number; + minimumReleaseInterval: number; releaseWindows: SCHEMA.EnvironmentPolicyReleaseWindow[]; }; isLoading: boolean; + onUpdate: (data: SCHEMA.UpdateEnvironmentPolicy) => Promise; }; export const RolloutAndTiming: React.FC = ({ environmentPolicy, isLoading, + onUpdate, }) => { const rolloutDuration = prettyMilliseconds(environmentPolicy.rolloutDuration); const minimumReleaseInterval = prettyMilliseconds( @@ -87,20 +86,12 @@ export const RolloutAndTiming: React.FC = ({ name: "releaseWindows", }); - const updatePolicy = api.environment.policy.update.useMutation(); - const invalidatePolicy = useInvalidatePolicy(environmentPolicy); - - const { id: policyId } = environmentPolicy; const onSubmit = form.handleSubmit((data) => { const { releaseWindows, rolloutDuration: durationString } = data; const rolloutDuration = ms(durationString); const minimumReleaseInterval = ms(data.minimumReleaseInterval); const updates = { rolloutDuration, releaseWindows, minimumReleaseInterval }; - updatePolicy - .mutateAsync({ id: policyId, data: updates }) - .then(() => form.reset(data)) - .then(() => invalidatePolicy()) - .catch((e) => toast.error(e.message)); + onUpdate(updates); }); return ( @@ -211,7 +202,6 @@ export const RolloutAndTiming: React.FC = ({ className="w-fit" onClick={() => append({ - policyId: environmentPolicy.id, recurrence: "weekly", startTime: new Date(), endTime: new Date(), @@ -287,10 +277,7 @@ export const RolloutAndTiming: React.FC = ({ )} /> - diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/reactflow/edges.ts b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/reactflow/edges.ts index 57eb78b3..b5d68338 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/reactflow/edges.ts +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/reactflow/edges.ts @@ -1,10 +1,6 @@ -import type { - EnvironmentPolicy, - EnvironmentPolicyDeployment, -} from "@ctrlplane/db/schema"; +import type * as SCHEMA from "@ctrlplane/db/schema"; import { MarkerType } from "reactflow"; import colors from "tailwindcss/colors"; -import { isPresent } from "ts-is-present"; const markerEnd = { type: MarkerType.Arrow, @@ -12,10 +8,14 @@ const markerEnd = { }; export const createEdgesWhereEnvironmentHasNoPolicy = ( - envs: Array<{ id: string; policyId?: string | null }>, + envs: SCHEMA.Environment[], + standalonePolicies: SCHEMA.EnvironmentPolicy[], ) => envs.map((e) => { - const source = isPresent(e.policyId) ? e.policyId : "trigger"; + const isUsingStandalonePolicy = standalonePolicies.some( + (p) => p.id === e.policyId, + ); + const source = isUsingStandalonePolicy ? e.policyId : "trigger"; return { id: source + "-" + e.id, source, @@ -35,7 +35,7 @@ export const createEdgesFromPolicyToEnvironment = ( })); export const createEdgesFromPolicyDeployment = ( - policyDeployments: Array, + policyDeployments: Array, ) => policyDeployments.map((p) => ({ id: p.id, @@ -45,8 +45,8 @@ export const createEdgesFromPolicyDeployment = ( })); export const createEdgesWherePolicyHasNoEnvironment = ( - policies: Array, - policyDeployments: Array, + policies: Array, + policyDeployments: Array, ) => policies .filter((t) => !policyDeployments.some((p) => p.policyId === t.id)) diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/release-channel-drawer/Usage.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/release-channel-drawer/Usage.tsx index fb38bad4..7767d53a 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/release-channel-drawer/Usage.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/release-channel-drawer/Usage.tsx @@ -4,7 +4,7 @@ import { IconFilter, IconPlant } from "@tabler/icons-react"; import { Button } from "@ctrlplane/ui/button"; -import { EnvironmentDrawerTab } from "../environment-drawer/EnvironmentDrawer"; +import { EnvironmentDrawerTab } from "../environment-drawer/tabs"; import { EnvironmentPolicyDrawerTab } from "../environment-policy-drawer/EnvironmentPolicyDrawer"; type UsageInfo = { diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/ReleaseEnvironmentCell.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/ReleaseEnvironmentCell.tsx index 98b13a0a..4abea218 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/ReleaseEnvironmentCell.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/ReleaseEnvironmentCell.tsx @@ -51,10 +51,10 @@ const ReleaseEnvironmentCell: React.FC = ({ api.release.blocked.useQuery([release.id]); const { data: approval, isLoading: isApprovalLoading } = - api.environment.policy.approval.statusByReleasePolicyId.useQuery( - { releaseId: release.id, policyId: environment.policyId ?? "" }, - { enabled: environment.policyId != null }, - ); + api.environment.policy.approval.statusByReleasePolicyId.useQuery({ + releaseId: release.id, + policyId: environment.policyId, + }); const blockedEnv = blockedEnvsResult?.find( (b) => b.environmentId === environment.id, diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/EnvironmentNode.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/EnvironmentNode.tsx index 33611c08..ef7ffb72 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/EnvironmentNode.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/EnvironmentNode.tsx @@ -147,14 +147,14 @@ const ReleaseChannelCheck: React.FC = ({ (rc) => rc.deploymentId === deploymentId, ); - const policyReleaseChannel = environment.data?.policy?.releaseChannels.find( + const policyReleaseChannel = environment.data?.policy.releaseChannels.find( (prc) => prc.deploymentId === deploymentId, ); const rcId = envReleaseChannel?.id ?? policyReleaseChannel?.id ?? null; - const { filter } = envReleaseChannel ?? - policyReleaseChannel ?? { filter: null }; + const { releaseFilter: filter } = envReleaseChannel ?? + policyReleaseChannel ?? { releaseFilter: null }; const versionFilter: ReleaseCondition = { type: ReleaseFilterType.Version, diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/useReleaseChannel.ts b/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/useReleaseChannel.ts index d1d97548..c4e50672 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/useReleaseChannel.ts +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/useReleaseChannel.ts @@ -20,12 +20,12 @@ export const useReleaseChannel = ( const envReleaseChannel = environment.data?.releaseChannels.find( (rc) => rc.deploymentId === deploymentId, ); - const policyReleaseChannel = environment.data?.policy?.releaseChannels.find( + const policyReleaseChannel = environment.data?.policy.releaseChannels.find( (prc) => prc.deploymentId === deploymentId, ); const rcId = envReleaseChannel?.id ?? policyReleaseChannel?.id ?? null; - const { filter } = envReleaseChannel ?? - policyReleaseChannel ?? { filter: null }; + const { releaseFilter: filter } = envReleaseChannel ?? + policyReleaseChannel ?? { releaseFilter: null }; const versionFilter: ReleaseCondition = { type: ReleaseFilterType.Version, diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/environments/EnvFlowBuilder.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/environments/EnvFlowBuilder.tsx index 8cfa9bfa..6a76f41d 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/environments/EnvFlowBuilder.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/environments/EnvFlowBuilder.tsx @@ -84,12 +84,6 @@ const useValidConnection = () => { if (target.type === NodeType.Policy && source.type === NodeType.Policy) return false; - if ( - target.type === NodeType.Environment && - source.type === NodeType.Policy - ) - return !isPresent(target.data.policyId); - return true; }, [getNodes, getEdges, getNode], @@ -175,6 +169,7 @@ export const EnvFlowBuilder: React.FC<{ policies: Array; policyDeployments: Array; }> = ({ systemId, envs, policies, policyDeployments }) => { + const standalonePolicies = policies.filter((p) => p.environmentId == null); const [nodes, _, onNodesChange] = useNodesState([ triggerNode, ...envs.map((env) => ({ @@ -183,7 +178,7 @@ export const EnvFlowBuilder: React.FC<{ position: { x: 0, y: 0 }, data: { ...env, label: env.name }, })), - ...policies.map((policy) => ({ + ...standalonePolicies.map((policy) => ({ id: policy.id, type: NodeType.Policy, position: { x: 0, y: 0 }, @@ -204,8 +199,11 @@ export const EnvFlowBuilder: React.FC<{ }); const [edges, __, onEdgesChange] = useEdgesState([ - ...createEdgesWhereEnvironmentHasNoPolicy(envs), - ...createEdgesWherePolicyHasNoEnvironment(policies, policyDeployments), + ...createEdgesWhereEnvironmentHasNoPolicy(envs, standalonePolicies), + ...createEdgesWherePolicyHasNoEnvironment( + standalonePolicies, + policyDeployments, + ), ...createEdgesFromPolicyDeployment(policyDeployments), ]); diff --git a/apps/webservice/src/app/api/v1/environments/route.ts b/apps/webservice/src/app/api/v1/environments/route.ts index fed76221..5c21c5f0 100644 --- a/apps/webservice/src/app/api/v1/environments/route.ts +++ b/apps/webservice/src/app/api/v1/environments/route.ts @@ -1,12 +1,10 @@ import type { PermissionChecker } from "@ctrlplane/auth/utils"; -import type { Tx } from "@ctrlplane/db"; import type { User } from "@ctrlplane/db/schema"; import { NextResponse } from "next/server"; import _ from "lodash"; -import { isPresent } from "ts-is-present"; import { z } from "zod"; -import { and, eq, inArray, takeFirst } from "@ctrlplane/db"; +import { and, createEnv, eq, inArray } from "@ctrlplane/db"; import * as schema from "@ctrlplane/db/schema"; import { createJobsForNewEnvironment } from "@ctrlplane/job-dispatch"; import { logger } from "@ctrlplane/logger"; @@ -24,19 +22,6 @@ const body = schema.createEnvironment.extend({ .optional(), }); -const createReleaseChannels = ( - db: Tx, - environmentId: string, - releaseChannels: { channelId: string; deploymentId: string }[], -) => - db.insert(schema.environmentReleaseChannel).values( - releaseChannels.map(({ channelId, deploymentId }) => ({ - environmentId, - channelId, - deploymentId, - })), - ); - export const POST = request() .use(authn) .use(parseBody(body)) @@ -63,42 +48,24 @@ export const POST = request() try { return ctx.db.transaction(async (tx) => { - const environment = await tx - .insert(schema.environment) - .values({ ...ctx.body }) - .returning() - .then(takeFirst); + const { releaseChannels, metadata, ...rest } = ctx.body; - const { metadata } = ctx.body; - if (metadata != null) - await tx.insert(schema.environmentMetadata).values( - Object.entries(metadata).map(([key, value]) => ({ - environmentId: environment.id, - key, - value, - })), - ); - - if ( - isPresent(ctx.body.releaseChannels) && - ctx.body.releaseChannels.length > 0 - ) { - const releaseChannels = await tx - .select() - .from(schema.releaseChannel) - .where( - inArray(schema.releaseChannel.id, ctx.body.releaseChannels), - ); - - await createReleaseChannels( - tx, - environment.id, - _.uniqBy(releaseChannels, (r) => r.deploymentId).map((r) => ({ + const channels = await tx + .select() + .from(schema.releaseChannel) + .where(inArray(schema.releaseChannel.id, releaseChannels)) + .then((rows) => + _.uniqBy(rows, (r) => r.deploymentId).map((r) => ({ channelId: r.id, deploymentId: r.deploymentId, })), ); - } + + const environment = await createEnv(tx, { + ...rest, + metadata, + releaseChannels: channels, + }); await createJobsForNewEnvironment(tx, environment); return NextResponse.json({ ...environment, metadata }); diff --git a/packages/api/src/router/environment-policy.ts b/packages/api/src/router/environment-policy.ts index 545d1483..6397b831 100644 --- a/packages/api/src/router/environment-policy.ts +++ b/packages/api/src/router/environment-policy.ts @@ -7,6 +7,7 @@ import { buildConflictUpdateColumns, eq, inArray, + sql, takeFirst, } from "@ctrlplane/db"; import { @@ -295,7 +296,7 @@ export const policyRouter = createTRPCRouter({ if (releaseWindows.length > 0) await db .insert(environmentPolicyReleaseWindow) - .values(releaseWindows) + .values(releaseWindows.map((r) => ({ ...r, policyId: input.id }))) .returning(); }); } @@ -316,10 +317,23 @@ export const policyRouter = createTRPCRouter({ }) .input(z.string().uuid()) .mutation(({ ctx, input }) => - ctx.db - .delete(environmentPolicy) - .where(eq(environmentPolicy.id, input)) - .returning() - .then(takeFirst), + ctx.db.transaction((db) => + db + .execute( + sql`UPDATE environment e + SET policy_id = ep.id + FROM environment_policy ep + WHERE e.id = ep.environment_id + AND e.policy_id = ${input} + `, + ) + .then(() => + db + .delete(environmentPolicy) + .where(eq(environmentPolicy.id, input)) + .returning() + .then(takeFirst), + ), + ), ), }); diff --git a/packages/api/src/router/environment.ts b/packages/api/src/router/environment.ts index eefe8e04..010ce45a 100644 --- a/packages/api/src/router/environment.ts +++ b/packages/api/src/router/environment.ts @@ -1,4 +1,3 @@ -import type { Tx } from "@ctrlplane/db"; import type { ResourceCondition } from "@ctrlplane/validators/resources"; import _ from "lodash"; import { isPresent } from "ts-is-present"; @@ -7,6 +6,7 @@ import { z } from "zod"; import { and, buildConflictUpdateColumns, + createEnv, eq, inArray, isNotNull, @@ -21,6 +21,7 @@ import { environmentMetadata, environmentPolicy, environmentPolicyReleaseChannel, + environmentPolicyReleaseWindow, environmentReleaseChannel, releaseChannel, resource, @@ -43,11 +44,6 @@ import { import { createTRPCRouter, protectedProcedure } from "../trpc"; import { policyRouter } from "./environment-policy"; -export const createEnv = async ( - db: Tx, - input: z.infer, -) => db.insert(environment).values(input).returning().then(takeFirst); - export const environmentRouter = createTRPCRouter({ policy: policyRouter, @@ -64,6 +60,7 @@ export const environmentRouter = createTRPCRouter({ .select({ releaseChannelEnvId: environmentReleaseChannel.environmentId, releaseChannelDeploymentId: releaseChannel.deploymentId, + releaseChannelDescription: releaseChannel.description, releaseChannelFilter: releaseChannel.releaseFilter, releaseChannelId: releaseChannel.id, releaseChannelName: releaseChannel.name, @@ -79,6 +76,7 @@ export const environmentRouter = createTRPCRouter({ .select({ releaseChannelPolicyId: environmentPolicyReleaseChannel.policyId, releaseChannelDeploymentId: releaseChannel.deploymentId, + releaseChannelDescription: releaseChannel.description, releaseChannelFilter: releaseChannel.releaseFilter, releaseChannelId: releaseChannel.id, releaseChannelName: releaseChannel.name, @@ -93,10 +91,14 @@ export const environmentRouter = createTRPCRouter({ return ctx.db .select() .from(environment) - .leftJoin( + .innerJoin( environmentPolicy, eq(environment.policyId, environmentPolicy.id), ) + .leftJoin( + environmentPolicyReleaseWindow, + eq(environmentPolicyReleaseWindow.policyId, environmentPolicy.id), + ) .innerJoin(system, eq(environment.systemId, system.id)) .leftJoin( envRCSubquery, @@ -114,23 +116,29 @@ export const environmentRouter = createTRPCRouter({ .then((rows) => { const env = rows.at(0); if (env == null) return null; - const policy = - env.environment_policy == null - ? null - : { - ...env.environment_policy, - releaseChannels: _.chain(rows) - .map((r) => r.policyRCSubquery) - .filter(isPresent) - .uniqBy((r) => r.releaseChannelId) - .map((r) => ({ - deploymentId: r.releaseChannelDeploymentId, - filter: r.releaseChannelFilter, - id: r.releaseChannelId, - name: r.releaseChannelName, - })) - .value(), - }; + + const policy = { + ...env.environment_policy, + releaseChannels: _.chain(rows) + .map((r) => r.policyRCSubquery) + .filter(isPresent) + .uniqBy((r) => r.releaseChannelId) + .map((r) => ({ + deploymentId: r.releaseChannelDeploymentId, + description: r.releaseChannelDescription, + releaseFilter: r.releaseChannelFilter, + id: r.releaseChannelId, + name: r.releaseChannelName, + })) + .value(), + releaseWindows: _.chain(rows) + .map((r) => r.environment_policy_release_window) + .filter(isPresent) + .uniqBy((r) => r.id) + .value(), + isOverride: + env.environment_policy.environmentId === env.environment.id, + }; const releaseChannels = _.chain(rows) .map((r) => r.envRCSubquery) @@ -138,7 +146,8 @@ export const environmentRouter = createTRPCRouter({ .uniqBy((r) => r.releaseChannelId) .map((r) => ({ deploymentId: r.releaseChannelDeploymentId, - filter: r.releaseChannelFilter, + releaseFilter: r.releaseChannelFilter, + description: r.releaseChannelDescription, id: r.releaseChannelId, name: r.releaseChannelName, })) @@ -246,9 +255,23 @@ export const environmentRouter = createTRPCRouter({ .where(eq(environment.id, input.id)) .then(takeFirst); + const overridePolicy = await ctx.db + .select() + .from(environmentPolicy) + .where(eq(environmentPolicy.environmentId, input.id)) + .then(takeFirst); + + const getPolicyId = () => { + if (input.data.policyId != null) return input.data.policyId; + if (input.data.policyId === null) return overridePolicy.id; + return oldEnv.environment.policyId; + }; + + const policyId = getPolicyId(); + const updatedEnv = await ctx.db .update(environment) - .set(input.data) + .set({ ...input.data, policyId }) .where(eq(environment.id, input.id)) .returning() .then(takeFirst); diff --git a/packages/api/src/router/system.ts b/packages/api/src/router/system.ts index e11901c6..8a08f9a8 100644 --- a/packages/api/src/router/system.ts +++ b/packages/api/src/router/system.ts @@ -2,7 +2,7 @@ import _ from "lodash"; import { isPresent } from "ts-is-present"; import { z } from "zod"; -import { and, asc, count, eq, like, takeFirst } from "@ctrlplane/db"; +import { and, asc, count, createEnv, eq, like, takeFirst } from "@ctrlplane/db"; import { createSystem, environment, @@ -13,7 +13,6 @@ import { import { Permission } from "@ctrlplane/validators/auth"; import { createTRPCRouter, protectedProcedure } from "../trpc"; -import { createEnv } from "./environment"; export const systemRouter = createTRPCRouter({ list: protectedProcedure diff --git a/packages/db/drizzle/0065_familiar_jazinda.sql b/packages/db/drizzle/0065_familiar_jazinda.sql new file mode 100644 index 00000000..6182a37f --- /dev/null +++ b/packages/db/drizzle/0065_familiar_jazinda.sql @@ -0,0 +1,27 @@ +BEGIN; +ALTER TABLE "environment_policy" ADD COLUMN "environment_id" uuid;--> statement-breakpoint +DO $$ BEGIN +ALTER TABLE "environment_policy" ADD CONSTRAINT "environment_policy_environment_id_environment_id_fk" FOREIGN KEY ("environment_id") REFERENCES "public"."environment"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION +WHEN duplicate_object THEN null; +END $$; + +-- Backfill default environment policies for each environment +INSERT INTO "environment_policy" ("name", "environment_id", "system_id") +SELECT + e."name" AS "name", + e."id" AS "environment_id", + e."system_id" AS "system_id" +FROM "environment" e +LEFT JOIN environment_policy ep ON e.id = ep.environment_id +WHERE ep.environment_id IS NULL; + +UPDATE "environment" +SET "policy_id" = ep."id" +FROM "environment_policy" ep +WHERE "environment"."id" = ep."environment_id" + AND "environment"."policy_id" IS NULL; + +ALTER TABLE "environment" ALTER COLUMN "policy_id" SET NOT NULL;--> statement-breakpoint + +COMMIT; \ No newline at end of file diff --git a/packages/db/drizzle/meta/0065_snapshot.json b/packages/db/drizzle/meta/0065_snapshot.json new file mode 100644 index 00000000..3d309fb5 --- /dev/null +++ b/packages/db/drizzle/meta/0065_snapshot.json @@ -0,0 +1,4614 @@ +{ + "id": "742cf221-2571-49af-891c-e22d807395ae", + "prevId": "8c075e46-20aa-4a9f-bd95-a061e71abfca", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "userId": { + "name": "userId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "token_type": { + "name": "token_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_providerAccountId_pk": { + "name": "account_provider_providerAccountId_pk", + "columns": ["provider", "providerAccountId"] + } + }, + "uniqueConstraints": {} + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["userId"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "image": { + "name": "image", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "active_workspace_id": { + "name": "active_workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false, + "default": "null" + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "null" + } + }, + "indexes": {}, + "foreignKeys": { + "user_active_workspace_id_workspace_id_fk": { + "name": "user_active_workspace_id_workspace_id_fk", + "tableFrom": "user", + "tableTo": "workspace", + "columnsFrom": ["active_workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user_api_key": { + "name": "user_api_key", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "key_preview": { + "name": "key_preview", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_prefix": { + "name": "key_prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_api_key_key_prefix_key_hash_index": { + "name": "user_api_key_key_prefix_key_hash_index", + "columns": [ + { + "expression": "key_prefix", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_api_key_user_id_user_id_fk": { + "name": "user_api_key_user_id_user_id_fk", + "tableFrom": "user_api_key", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.dashboard": { + "name": "dashboard", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "dashboard_workspace_id_workspace_id_fk": { + "name": "dashboard_workspace_id_workspace_id_fk", + "tableFrom": "dashboard", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.dashboard_widget": { + "name": "dashboard_widget", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "dashboard_id": { + "name": "dashboard_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "widget": { + "name": "widget", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "x": { + "name": "x", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "y": { + "name": "y", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "w": { + "name": "w", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "h": { + "name": "h", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "dashboard_widget_dashboard_id_dashboard_id_fk": { + "name": "dashboard_widget_dashboard_id_dashboard_id_fk", + "tableFrom": "dashboard_widget", + "tableTo": "dashboard", + "columnsFrom": ["dashboard_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.deployment_variable": { + "name": "deployment_variable", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "deployment_id": { + "name": "deployment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "default_value_id": { + "name": "default_value_id", + "type": "uuid", + "primaryKey": false, + "notNull": false, + "default": "NULL" + }, + "schema": { + "name": "schema", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "deployment_variable_deployment_id_key_index": { + "name": "deployment_variable_deployment_id_key_index", + "columns": [ + { + "expression": "deployment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "deployment_variable_deployment_id_deployment_id_fk": { + "name": "deployment_variable_deployment_id_deployment_id_fk", + "tableFrom": "deployment_variable", + "tableTo": "deployment", + "columnsFrom": ["deployment_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "deployment_variable_default_value_id_deployment_variable_value_id_fk": { + "name": "deployment_variable_default_value_id_deployment_variable_value_id_fk", + "tableFrom": "deployment_variable", + "tableTo": "deployment_variable_value", + "columnsFrom": ["default_value_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.deployment_variable_set": { + "name": "deployment_variable_set", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "deployment_id": { + "name": "deployment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "variable_set_id": { + "name": "variable_set_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "deployment_variable_set_deployment_id_variable_set_id_index": { + "name": "deployment_variable_set_deployment_id_variable_set_id_index", + "columns": [ + { + "expression": "deployment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "variable_set_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "deployment_variable_set_deployment_id_deployment_id_fk": { + "name": "deployment_variable_set_deployment_id_deployment_id_fk", + "tableFrom": "deployment_variable_set", + "tableTo": "deployment", + "columnsFrom": ["deployment_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "deployment_variable_set_variable_set_id_variable_set_id_fk": { + "name": "deployment_variable_set_variable_set_id_variable_set_id_fk", + "tableFrom": "deployment_variable_set", + "tableTo": "variable_set", + "columnsFrom": ["variable_set_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.deployment_variable_value": { + "name": "deployment_variable_value", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "variable_id": { + "name": "variable_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "resource_filter": { + "name": "resource_filter", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "NULL" + } + }, + "indexes": { + "deployment_variable_value_variable_id_value_index": { + "name": "deployment_variable_value_variable_id_value_index", + "columns": [ + { + "expression": "variable_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "value", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "deployment_variable_value_variable_id_deployment_variable_id_fk": { + "name": "deployment_variable_value_variable_id_deployment_variable_id_fk", + "tableFrom": "deployment_variable_value", + "tableTo": "deployment_variable", + "columnsFrom": ["variable_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.deployment": { + "name": "deployment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "system_id": { + "name": "system_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "job_agent_id": { + "name": "job_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "job_agent_config": { + "name": "job_agent_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "retry_count": { + "name": "retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "timeout": { + "name": "timeout", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": "NULL" + }, + "resource_filter": { + "name": "resource_filter", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "NULL" + } + }, + "indexes": { + "deployment_system_id_slug_index": { + "name": "deployment_system_id_slug_index", + "columns": [ + { + "expression": "system_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "deployment_system_id_system_id_fk": { + "name": "deployment_system_id_system_id_fk", + "tableFrom": "deployment", + "tableTo": "system", + "columnsFrom": ["system_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "deployment_job_agent_id_job_agent_id_fk": { + "name": "deployment_job_agent_id_job_agent_id_fk", + "tableFrom": "deployment", + "tableTo": "job_agent", + "columnsFrom": ["job_agent_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.deployment_meta_dependency": { + "name": "deployment_meta_dependency", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "deployment_id": { + "name": "deployment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "depends_on_id": { + "name": "depends_on_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "deployment_meta_dependency_depends_on_id_deployment_id_index": { + "name": "deployment_meta_dependency_depends_on_id_deployment_id_index", + "columns": [ + { + "expression": "depends_on_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "deployment_meta_dependency_deployment_id_deployment_id_fk": { + "name": "deployment_meta_dependency_deployment_id_deployment_id_fk", + "tableFrom": "deployment_meta_dependency", + "tableTo": "deployment", + "columnsFrom": ["deployment_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "deployment_meta_dependency_depends_on_id_deployment_id_fk": { + "name": "deployment_meta_dependency_depends_on_id_deployment_id_fk", + "tableFrom": "deployment_meta_dependency", + "tableTo": "deployment", + "columnsFrom": ["depends_on_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.environment_policy_deployment": { + "name": "environment_policy_deployment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "environment_policy_deployment_policy_id_environment_id_index": { + "name": "environment_policy_deployment_policy_id_environment_id_index", + "columns": [ + { + "expression": "policy_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_policy_deployment_policy_id_environment_policy_id_fk": { + "name": "environment_policy_deployment_policy_id_environment_policy_id_fk", + "tableFrom": "environment_policy_deployment", + "tableTo": "environment_policy", + "columnsFrom": ["policy_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_policy_deployment_environment_id_environment_id_fk": { + "name": "environment_policy_deployment_environment_id_environment_id_fk", + "tableFrom": "environment_policy_deployment", + "tableTo": "environment", + "columnsFrom": ["environment_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.environment": { + "name": "environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "system_id": { + "name": "system_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "resource_filter": { + "name": "resource_filter", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "NULL" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environment_system_id_name_index": { + "name": "environment_system_id_name_index", + "columns": [ + { + "expression": "system_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_system_id_system_id_fk": { + "name": "environment_system_id_system_id_fk", + "tableFrom": "environment", + "tableTo": "system", + "columnsFrom": ["system_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_policy_id_environment_policy_id_fk": { + "name": "environment_policy_id_environment_policy_id_fk", + "tableFrom": "environment", + "tableTo": "environment_policy", + "columnsFrom": ["policy_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.environment_metadata": { + "name": "environment_metadata", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "environment_metadata_key_environment_id_index": { + "name": "environment_metadata_key_environment_id_index", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_metadata_environment_id_environment_id_fk": { + "name": "environment_metadata_environment_id_environment_id_fk", + "tableFrom": "environment_metadata", + "tableTo": "environment", + "columnsFrom": ["environment_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.environment_policy": { + "name": "environment_policy", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system_id": { + "name": "system_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approval_required": { + "name": "approval_required", + "type": "environment_policy_approval_requirement", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "success_status": { + "name": "success_status", + "type": "environment_policy_deployment_success_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'all'" + }, + "minimum_success": { + "name": "minimum_success", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "concurrency_limit": { + "name": "concurrency_limit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": "NULL" + }, + "rollout_duration": { + "name": "rollout_duration", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "minimum_release_interval": { + "name": "minimum_release_interval", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "release_sequencing": { + "name": "release_sequencing", + "type": "release_sequencing_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'cancel'" + } + }, + "indexes": {}, + "foreignKeys": { + "environment_policy_system_id_system_id_fk": { + "name": "environment_policy_system_id_system_id_fk", + "tableFrom": "environment_policy", + "tableTo": "system", + "columnsFrom": ["system_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_policy_environment_id_environment_id_fk": { + "name": "environment_policy_environment_id_environment_id_fk", + "tableFrom": "environment_policy", + "tableTo": "environment", + "columnsFrom": ["environment_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.environment_policy_approval": { + "name": "environment_policy_approval", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "release_id": { + "name": "release_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "approval_status_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false, + "default": "NULL" + } + }, + "indexes": { + "environment_policy_approval_policy_id_release_id_index": { + "name": "environment_policy_approval_policy_id_release_id_index", + "columns": [ + { + "expression": "policy_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "release_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_policy_approval_policy_id_environment_policy_id_fk": { + "name": "environment_policy_approval_policy_id_environment_policy_id_fk", + "tableFrom": "environment_policy_approval", + "tableTo": "environment_policy", + "columnsFrom": ["policy_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_policy_approval_release_id_release_id_fk": { + "name": "environment_policy_approval_release_id_release_id_fk", + "tableFrom": "environment_policy_approval", + "tableTo": "release", + "columnsFrom": ["release_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_policy_approval_user_id_user_id_fk": { + "name": "environment_policy_approval_user_id_user_id_fk", + "tableFrom": "environment_policy_approval", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.environment_policy_release_window": { + "name": "environment_policy_release_window", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "start_time": { + "name": "start_time", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true + }, + "end_time": { + "name": "end_time", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true + }, + "recurrence": { + "name": "recurrence", + "type": "recurrence_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "environment_policy_release_window_policy_id_environment_policy_id_fk": { + "name": "environment_policy_release_window_policy_id_environment_policy_id_fk", + "tableFrom": "environment_policy_release_window", + "tableTo": "environment_policy", + "columnsFrom": ["policy_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.event": { + "name": "event", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.hook": { + "name": "hook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.runhook": { + "name": "runhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "hook_id": { + "name": "hook_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "runbook_id": { + "name": "runbook_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "runhook_hook_id_runbook_id_index": { + "name": "runhook_hook_id_runbook_id_index", + "columns": [ + { + "expression": "hook_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "runbook_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "runhook_hook_id_hook_id_fk": { + "name": "runhook_hook_id_hook_id_fk", + "tableFrom": "runhook", + "tableTo": "hook", + "columnsFrom": ["hook_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "runhook_runbook_id_runbook_id_fk": { + "name": "runhook_runbook_id_runbook_id_fk", + "tableFrom": "runhook", + "tableTo": "runbook", + "columnsFrom": ["runbook_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.github_entity": { + "name": "github_entity", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "installation_id": { + "name": "installation_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "github_entity_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'organization'" + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "added_by_user_id": { + "name": "added_by_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "unique_installation_workspace": { + "name": "unique_installation_workspace", + "columns": [ + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "github_entity_added_by_user_id_user_id_fk": { + "name": "github_entity_added_by_user_id_user_id_fk", + "tableFrom": "github_entity", + "tableTo": "user", + "columnsFrom": ["added_by_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "github_entity_workspace_id_workspace_id_fk": { + "name": "github_entity_workspace_id_workspace_id_fk", + "tableFrom": "github_entity", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.github_user": { + "name": "github_user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "github_user_id": { + "name": "github_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "github_username": { + "name": "github_username", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "github_user_user_id_user_id_fk": { + "name": "github_user_user_id_user_id_fk", + "tableFrom": "github_user", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.job_resource_relationship": { + "name": "job_resource_relationship", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "job_id": { + "name": "job_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "resource_identifier": { + "name": "resource_identifier", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "job_resource_relationship_job_id_resource_identifier_index": { + "name": "job_resource_relationship_job_id_resource_identifier_index", + "columns": [ + { + "expression": "job_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "job_resource_relationship_job_id_job_id_fk": { + "name": "job_resource_relationship_job_id_job_id_fk", + "tableFrom": "job_resource_relationship", + "tableTo": "job", + "columnsFrom": ["job_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.resource": { + "name": "resource", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "locked_at": { + "name": "locked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "resource_identifier_workspace_id_index": { + "name": "resource_identifier_workspace_id_index", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "resource_provider_id_resource_provider_id_fk": { + "name": "resource_provider_id_resource_provider_id_fk", + "tableFrom": "resource", + "tableTo": "resource_provider", + "columnsFrom": ["provider_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "resource_workspace_id_workspace_id_fk": { + "name": "resource_workspace_id_workspace_id_fk", + "tableFrom": "resource", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.resource_metadata": { + "name": "resource_metadata", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "resource_id": { + "name": "resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "resource_metadata_key_resource_id_index": { + "name": "resource_metadata_key_resource_id_index", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "resource_metadata_resource_id_resource_id_fk": { + "name": "resource_metadata_resource_id_resource_id_fk", + "tableFrom": "resource_metadata", + "tableTo": "resource", + "columnsFrom": ["resource_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.resource_relationship": { + "name": "resource_relationship", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "from_identifier": { + "name": "from_identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "to_identifier": { + "name": "to_identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "resource_relationship_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "resource_relationship_to_identifier_from_identifier_index": { + "name": "resource_relationship_to_identifier_from_identifier_index", + "columns": [ + { + "expression": "to_identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "from_identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "resource_relationship_workspace_id_workspace_id_fk": { + "name": "resource_relationship_workspace_id_workspace_id_fk", + "tableFrom": "resource_relationship", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.resource_schema": { + "name": "resource_schema", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "json_schema": { + "name": "json_schema", + "type": "json", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "resource_schema_version_kind_workspace_id_index": { + "name": "resource_schema_version_kind_workspace_id_index", + "columns": [ + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "resource_schema_workspace_id_workspace_id_fk": { + "name": "resource_schema_workspace_id_workspace_id_fk", + "tableFrom": "resource_schema", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.resource_variable": { + "name": "resource_variable", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "resource_id": { + "name": "resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "sensitive": { + "name": "sensitive", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "resource_variable_resource_id_key_index": { + "name": "resource_variable_resource_id_key_index", + "columns": [ + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "resource_variable_resource_id_resource_id_fk": { + "name": "resource_variable_resource_id_resource_id_fk", + "tableFrom": "resource_variable", + "tableTo": "resource", + "columnsFrom": ["resource_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.resource_view": { + "name": "resource_view", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "filter": { + "name": "filter", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "resource_view_workspace_id_workspace_id_fk": { + "name": "resource_view_workspace_id_workspace_id_fk", + "tableFrom": "resource_view", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.azure_tenant": { + "name": "azure_tenant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "azure_tenant_tenant_id_index": { + "name": "azure_tenant_tenant_id_index", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "azure_tenant_workspace_id_workspace_id_fk": { + "name": "azure_tenant_workspace_id_workspace_id_fk", + "tableFrom": "azure_tenant", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.resource_provider": { + "name": "resource_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "resource_provider_workspace_id_name_index": { + "name": "resource_provider_workspace_id_name_index", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "resource_provider_workspace_id_workspace_id_fk": { + "name": "resource_provider_workspace_id_workspace_id_fk", + "tableFrom": "resource_provider", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.resource_provider_aws": { + "name": "resource_provider_aws", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "resource_provider_id": { + "name": "resource_provider_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "aws_role_arns": { + "name": "aws_role_arns", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "import_eks": { + "name": "import_eks", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "import_vpc": { + "name": "import_vpc", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "resource_provider_aws_resource_provider_id_resource_provider_id_fk": { + "name": "resource_provider_aws_resource_provider_id_resource_provider_id_fk", + "tableFrom": "resource_provider_aws", + "tableTo": "resource_provider", + "columnsFrom": ["resource_provider_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.resource_provider_azure": { + "name": "resource_provider_azure", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "resource_provider_id": { + "name": "resource_provider_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "subscription_id": { + "name": "subscription_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "resource_provider_azure_resource_provider_id_resource_provider_id_fk": { + "name": "resource_provider_azure_resource_provider_id_resource_provider_id_fk", + "tableFrom": "resource_provider_azure", + "tableTo": "resource_provider", + "columnsFrom": ["resource_provider_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "resource_provider_azure_tenant_id_azure_tenant_id_fk": { + "name": "resource_provider_azure_tenant_id_azure_tenant_id_fk", + "tableFrom": "resource_provider_azure", + "tableTo": "azure_tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.resource_provider_google": { + "name": "resource_provider_google", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "resource_provider_id": { + "name": "resource_provider_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_ids": { + "name": "project_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "import_gke": { + "name": "import_gke", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "import_namespaces": { + "name": "import_namespaces", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "import_vcluster": { + "name": "import_vcluster", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "import_vms": { + "name": "import_vms", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "import_vpc": { + "name": "import_vpc", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "resource_provider_google_resource_provider_id_resource_provider_id_fk": { + "name": "resource_provider_google_resource_provider_id_resource_provider_id_fk", + "tableFrom": "resource_provider_google", + "tableTo": "resource_provider", + "columnsFrom": ["resource_provider_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.release": { + "name": "release", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "job_agent_config": { + "name": "job_agent_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "deployment_id": { + "name": "deployment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "release_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'ready'" + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "release_deployment_id_version_index": { + "name": "release_deployment_id_version_index", + "columns": [ + { + "expression": "deployment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "release_deployment_id_deployment_id_fk": { + "name": "release_deployment_id_deployment_id_fk", + "tableFrom": "release", + "tableTo": "deployment", + "columnsFrom": ["deployment_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.release_channel": { + "name": "release_channel", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "deployment_id": { + "name": "deployment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "release_filter": { + "name": "release_filter", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "NULL" + } + }, + "indexes": { + "release_channel_deployment_id_name_index": { + "name": "release_channel_deployment_id_name_index", + "columns": [ + { + "expression": "deployment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "release_channel_deployment_id_deployment_id_fk": { + "name": "release_channel_deployment_id_deployment_id_fk", + "tableFrom": "release_channel", + "tableTo": "deployment", + "columnsFrom": ["deployment_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.release_dependency": { + "name": "release_dependency", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "release_id": { + "name": "release_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "deployment_id": { + "name": "deployment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "release_filter": { + "name": "release_filter", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "NULL" + } + }, + "indexes": { + "release_dependency_release_id_deployment_id_index": { + "name": "release_dependency_release_id_deployment_id_index", + "columns": [ + { + "expression": "release_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "release_dependency_release_id_release_id_fk": { + "name": "release_dependency_release_id_release_id_fk", + "tableFrom": "release_dependency", + "tableTo": "release", + "columnsFrom": ["release_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "release_dependency_deployment_id_deployment_id_fk": { + "name": "release_dependency_deployment_id_deployment_id_fk", + "tableFrom": "release_dependency", + "tableTo": "deployment", + "columnsFrom": ["deployment_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.release_job_trigger": { + "name": "release_job_trigger", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "job_id": { + "name": "job_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "release_job_trigger_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "caused_by_id": { + "name": "caused_by_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "release_id": { + "name": "release_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "release_job_trigger_job_id_job_id_fk": { + "name": "release_job_trigger_job_id_job_id_fk", + "tableFrom": "release_job_trigger", + "tableTo": "job", + "columnsFrom": ["job_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "release_job_trigger_caused_by_id_user_id_fk": { + "name": "release_job_trigger_caused_by_id_user_id_fk", + "tableFrom": "release_job_trigger", + "tableTo": "user", + "columnsFrom": ["caused_by_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "release_job_trigger_release_id_release_id_fk": { + "name": "release_job_trigger_release_id_release_id_fk", + "tableFrom": "release_job_trigger", + "tableTo": "release", + "columnsFrom": ["release_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "release_job_trigger_resource_id_resource_id_fk": { + "name": "release_job_trigger_resource_id_resource_id_fk", + "tableFrom": "release_job_trigger", + "tableTo": "resource", + "columnsFrom": ["resource_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "release_job_trigger_environment_id_environment_id_fk": { + "name": "release_job_trigger_environment_id_environment_id_fk", + "tableFrom": "release_job_trigger", + "tableTo": "environment", + "columnsFrom": ["environment_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "release_job_trigger_job_id_unique": { + "name": "release_job_trigger_job_id_unique", + "nullsNotDistinct": false, + "columns": ["job_id"] + } + } + }, + "public.release_metadata": { + "name": "release_metadata", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "release_id": { + "name": "release_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "release_metadata_key_release_id_index": { + "name": "release_metadata_key_release_id_index", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "release_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "release_metadata_release_id_release_id_fk": { + "name": "release_metadata_release_id_release_id_fk", + "tableFrom": "release_metadata", + "tableTo": "release", + "columnsFrom": ["release_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.system": { + "name": "system", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "system_workspace_id_slug_index": { + "name": "system_workspace_id_slug_index", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "system_workspace_id_workspace_id_fk": { + "name": "system_workspace_id_workspace_id_fk", + "tableFrom": "system", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.runbook": { + "name": "runbook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system_id": { + "name": "system_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "job_agent_id": { + "name": "job_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "job_agent_config": { + "name": "job_agent_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + } + }, + "indexes": {}, + "foreignKeys": { + "runbook_system_id_system_id_fk": { + "name": "runbook_system_id_system_id_fk", + "tableFrom": "runbook", + "tableTo": "system", + "columnsFrom": ["system_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "runbook_job_agent_id_job_agent_id_fk": { + "name": "runbook_job_agent_id_job_agent_id_fk", + "tableFrom": "runbook", + "tableTo": "job_agent", + "columnsFrom": ["job_agent_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.runbook_job_trigger": { + "name": "runbook_job_trigger", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "job_id": { + "name": "job_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "runbook_id": { + "name": "runbook_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "runbook_job_trigger_job_id_job_id_fk": { + "name": "runbook_job_trigger_job_id_job_id_fk", + "tableFrom": "runbook_job_trigger", + "tableTo": "job", + "columnsFrom": ["job_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "runbook_job_trigger_runbook_id_runbook_id_fk": { + "name": "runbook_job_trigger_runbook_id_runbook_id_fk", + "tableFrom": "runbook_job_trigger", + "tableTo": "runbook", + "columnsFrom": ["runbook_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "runbook_job_trigger_job_id_unique": { + "name": "runbook_job_trigger_job_id_unique", + "nullsNotDistinct": false, + "columns": ["job_id"] + } + } + }, + "public.team": { + "name": "team", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "team_workspace_id_workspace_id_fk": { + "name": "team_workspace_id_workspace_id_fk", + "tableFrom": "team", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.team_member": { + "name": "team_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "team_member_team_id_user_id_index": { + "name": "team_member_team_id_user_id_index", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_member_team_id_team_id_fk": { + "name": "team_member_team_id_team_id_fk", + "tableFrom": "team_member", + "tableTo": "team", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_member_user_id_user_id_fk": { + "name": "team_member_user_id_user_id_fk", + "tableFrom": "team_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.job": { + "name": "job", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "job_agent_id": { + "name": "job_agent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "job_agent_config": { + "name": "job_agent_config", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "job_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "job_reason", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'policy_passing'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "job_created_at_idx": { + "name": "job_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_status_idx": { + "name": "job_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "job_job_agent_id_job_agent_id_fk": { + "name": "job_job_agent_id_job_agent_id_fk", + "tableFrom": "job", + "tableTo": "job_agent", + "columnsFrom": ["job_agent_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.job_metadata": { + "name": "job_metadata", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "job_id": { + "name": "job_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "job_metadata_key_job_id_index": { + "name": "job_metadata_key_job_id_index", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "job_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "job_metadata_job_id_job_id_fk": { + "name": "job_metadata_job_id_job_id_fk", + "tableFrom": "job_metadata", + "tableTo": "job", + "columnsFrom": ["job_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.job_variable": { + "name": "job_variable", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "job_id": { + "name": "job_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "sensitive": { + "name": "sensitive", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "job_variable_job_id_key_index": { + "name": "job_variable_job_id_key_index", + "columns": [ + { + "expression": "job_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "job_variable_job_id_job_id_fk": { + "name": "job_variable_job_id_job_id_fk", + "tableFrom": "job_variable", + "tableTo": "job", + "columnsFrom": ["job_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.workspace": { + "name": "workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "google_service_account_email": { + "name": "google_service_account_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "aws_role_arn": { + "name": "aws_role_arn", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_slug_unique": { + "name": "workspace_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + } + } + }, + "public.workspace_email_domain_matching": { + "name": "workspace_email_domain_matching", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "verification_code": { + "name": "verification_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "verification_email": { + "name": "verification_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_email_domain_matching_workspace_id_domain_index": { + "name": "workspace_email_domain_matching_workspace_id_domain_index", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_email_domain_matching_workspace_id_workspace_id_fk": { + "name": "workspace_email_domain_matching_workspace_id_workspace_id_fk", + "tableFrom": "workspace_email_domain_matching", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_email_domain_matching_role_id_role_id_fk": { + "name": "workspace_email_domain_matching_role_id_role_id_fk", + "tableFrom": "workspace_email_domain_matching", + "tableTo": "role", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.variable_set": { + "name": "variable_set", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system_id": { + "name": "system_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "variable_set_system_id_system_id_fk": { + "name": "variable_set_system_id_system_id_fk", + "tableFrom": "variable_set", + "tableTo": "system", + "columnsFrom": ["system_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.variable_set_environment": { + "name": "variable_set_environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "variable_set_id": { + "name": "variable_set_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "variable_set_environment_variable_set_id_variable_set_id_fk": { + "name": "variable_set_environment_variable_set_id_variable_set_id_fk", + "tableFrom": "variable_set_environment", + "tableTo": "variable_set", + "columnsFrom": ["variable_set_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "variable_set_environment_environment_id_environment_id_fk": { + "name": "variable_set_environment_environment_id_environment_id_fk", + "tableFrom": "variable_set_environment", + "tableTo": "environment", + "columnsFrom": ["environment_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.variable_set_value": { + "name": "variable_set_value", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "variable_set_id": { + "name": "variable_set_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "variable_set_value_variable_set_id_key_index": { + "name": "variable_set_value_variable_set_id_key_index", + "columns": [ + { + "expression": "variable_set_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "variable_set_value_variable_set_id_variable_set_id_fk": { + "name": "variable_set_value_variable_set_id_variable_set_id_fk", + "tableFrom": "variable_set_value", + "tableTo": "variable_set", + "columnsFrom": ["variable_set_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.workspace_invite_token": { + "name": "workspace_invite_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "role_id": { + "name": "role_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_invite_token_role_id_role_id_fk": { + "name": "workspace_invite_token_role_id_role_id_fk", + "tableFrom": "workspace_invite_token", + "tableTo": "role", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_invite_token_workspace_id_workspace_id_fk": { + "name": "workspace_invite_token_workspace_id_workspace_id_fk", + "tableFrom": "workspace_invite_token", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_invite_token_created_by_user_id_fk": { + "name": "workspace_invite_token_created_by_user_id_fk", + "tableFrom": "workspace_invite_token", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_invite_token_token_unique": { + "name": "workspace_invite_token_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + } + }, + "public.resource_metadata_group": { + "name": "resource_metadata_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "keys": { + "name": "keys", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "include_null_combinations": { + "name": "include_null_combinations", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "resource_metadata_group_workspace_id_workspace_id_fk": { + "name": "resource_metadata_group_workspace_id_workspace_id_fk", + "tableFrom": "resource_metadata_group", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.runbook_variable": { + "name": "runbook_variable", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "runbook_id": { + "name": "runbook_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "schema": { + "name": "schema", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "required": { + "name": "required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "runbook_variable_runbook_id_key_index": { + "name": "runbook_variable_runbook_id_key_index", + "columns": [ + { + "expression": "runbook_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "runbook_variable_runbook_id_runbook_id_fk": { + "name": "runbook_variable_runbook_id_runbook_id_fk", + "tableFrom": "runbook_variable", + "tableTo": "runbook", + "columnsFrom": ["runbook_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.entity_role": { + "name": "entity_role", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "role_id": { + "name": "role_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "entity_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "scope_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "entity_role_role_id_entity_type_entity_id_scope_id_scope_type_index": { + "name": "entity_role_role_id_entity_type_entity_id_scope_id_scope_type_index", + "columns": [ + { + "expression": "role_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "entity_role_role_id_role_id_fk": { + "name": "entity_role_role_id_role_id_fk", + "tableFrom": "entity_role", + "tableTo": "role", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.role": { + "name": "role", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "role_workspace_id_workspace_id_fk": { + "name": "role_workspace_id_workspace_id_fk", + "tableFrom": "role", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.role_permission": { + "name": "role_permission", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "role_id": { + "name": "role_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "permission": { + "name": "permission", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "role_permission_role_id_permission_index": { + "name": "role_permission_role_id_permission_index", + "columns": [ + { + "expression": "role_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "role_permission_role_id_role_id_fk": { + "name": "role_permission_role_id_role_id_fk", + "tableFrom": "role_permission", + "tableTo": "role", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.job_agent": { + "name": "job_agent", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "workspace_id": { + "name": "workspace_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + } + }, + "indexes": { + "job_agent_workspace_id_name_index": { + "name": "job_agent_workspace_id_name_index", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "job_agent_workspace_id_workspace_id_fk": { + "name": "job_agent_workspace_id_workspace_id_fk", + "tableFrom": "job_agent", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.environment_policy_release_channel": { + "name": "environment_policy_release_channel", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "policy_id": { + "name": "policy_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "deployment_id": { + "name": "deployment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "environment_policy_release_channel_policy_id_channel_id_index": { + "name": "environment_policy_release_channel_policy_id_channel_id_index", + "columns": [ + { + "expression": "policy_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_policy_release_channel_policy_id_deployment_id_index": { + "name": "environment_policy_release_channel_policy_id_deployment_id_index", + "columns": [ + { + "expression": "policy_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_policy_release_channel_policy_id_environment_policy_id_fk": { + "name": "environment_policy_release_channel_policy_id_environment_policy_id_fk", + "tableFrom": "environment_policy_release_channel", + "tableTo": "environment_policy", + "columnsFrom": ["policy_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_policy_release_channel_channel_id_release_channel_id_fk": { + "name": "environment_policy_release_channel_channel_id_release_channel_id_fk", + "tableFrom": "environment_policy_release_channel", + "tableTo": "release_channel", + "columnsFrom": ["channel_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_policy_release_channel_deployment_id_deployment_id_fk": { + "name": "environment_policy_release_channel_deployment_id_deployment_id_fk", + "tableFrom": "environment_policy_release_channel", + "tableTo": "deployment", + "columnsFrom": ["deployment_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.environment_release_channel": { + "name": "environment_release_channel", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "environment_id": { + "name": "environment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "deployment_id": { + "name": "deployment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "environment_release_channel_environment_id_channel_id_index": { + "name": "environment_release_channel_environment_id_channel_id_index", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_release_channel_environment_id_deployment_id_index": { + "name": "environment_release_channel_environment_id_deployment_id_index", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_release_channel_environment_id_environment_id_fk": { + "name": "environment_release_channel_environment_id_environment_id_fk", + "tableFrom": "environment_release_channel", + "tableTo": "environment", + "columnsFrom": ["environment_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_release_channel_channel_id_release_channel_id_fk": { + "name": "environment_release_channel_channel_id_release_channel_id_fk", + "tableFrom": "environment_release_channel", + "tableTo": "release_channel", + "columnsFrom": ["channel_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "environment_release_channel_deployment_id_deployment_id_fk": { + "name": "environment_release_channel_deployment_id_deployment_id_fk", + "tableFrom": "environment_release_channel", + "tableTo": "deployment", + "columnsFrom": ["deployment_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": { + "public.environment_policy_approval_requirement": { + "name": "environment_policy_approval_requirement", + "schema": "public", + "values": ["manual", "automatic"] + }, + "public.approval_status_type": { + "name": "approval_status_type", + "schema": "public", + "values": ["pending", "approved", "rejected"] + }, + "public.environment_policy_deployment_success_type": { + "name": "environment_policy_deployment_success_type", + "schema": "public", + "values": ["all", "some", "optional"] + }, + "public.recurrence_type": { + "name": "recurrence_type", + "schema": "public", + "values": ["hourly", "daily", "weekly", "monthly"] + }, + "public.release_sequencing_type": { + "name": "release_sequencing_type", + "schema": "public", + "values": ["wait", "cancel"] + }, + "public.github_entity_type": { + "name": "github_entity_type", + "schema": "public", + "values": ["organization", "user"] + }, + "public.resource_relationship_type": { + "name": "resource_relationship_type", + "schema": "public", + "values": ["associated_with", "depends_on"] + }, + "public.release_job_trigger_type": { + "name": "release_job_trigger_type", + "schema": "public", + "values": [ + "new_release", + "release_updated", + "new_resource", + "resource_changed", + "api", + "redeploy", + "force_deploy", + "new_environment", + "variable_changed", + "retry" + ] + }, + "public.release_status": { + "name": "release_status", + "schema": "public", + "values": ["building", "ready", "failed"] + }, + "public.job_reason": { + "name": "job_reason", + "schema": "public", + "values": [ + "policy_passing", + "policy_override", + "env_policy_override", + "config_policy_override" + ] + }, + "public.job_status": { + "name": "job_status", + "schema": "public", + "values": [ + "cancelled", + "skipped", + "in_progress", + "action_required", + "pending", + "failure", + "invalid_job_agent", + "invalid_integration", + "external_run_not_found", + "successful" + ] + }, + "public.entity_type": { + "name": "entity_type", + "schema": "public", + "values": ["user", "team"] + }, + "public.scope_type": { + "name": "scope_type", + "schema": "public", + "values": [ + "release", + "releaseChannel", + "resource", + "resourceProvider", + "resourceMetadataGroup", + "workspace", + "environment", + "environmentPolicy", + "deploymentVariable", + "variableSet", + "system", + "deployment", + "job", + "jobAgent", + "runbook", + "resourceView" + ] + } + }, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json index c28fd231..427a0ced 100644 --- a/packages/db/drizzle/meta/_journal.json +++ b/packages/db/drizzle/meta/_journal.json @@ -456,6 +456,13 @@ "when": 1737862789255, "tag": "0064_misty_stryfe", "breakpoints": true + }, + { + "idx": 65, + "version": "7", + "when": 1738098519525, + "tag": "0065_familiar_jazinda", + "breakpoints": true } ] } diff --git a/packages/db/src/create-env.ts b/packages/db/src/create-env.ts new file mode 100644 index 00000000..dac226c6 --- /dev/null +++ b/packages/db/src/create-env.ts @@ -0,0 +1,60 @@ +import type { z } from "zod"; +import { eq } from "drizzle-orm"; + +import type { Tx } from "./common.js"; +import { takeFirst } from "./common.js"; +import * as SCHEMA from "./schema/index.js"; +import { environment, environmentPolicy } from "./schema/index.js"; + +const createReleaseChannels = ( + db: Tx, + environmentId: string, + releaseChannels: { channelId: string; deploymentId: string }[], +) => + db.insert(SCHEMA.environmentReleaseChannel).values( + releaseChannels.map(({ channelId, deploymentId }) => ({ + environmentId, + channelId, + deploymentId, + })), + ); + +export const createEnv = async ( + db: Tx, + input: z.infer, + metadata?: Record, + releaseChannels?: { channelId: string; deploymentId: string }[], +) => { + const overridePolicyId = await db + .insert(environmentPolicy) + .values({ name: input.name, systemId: input.systemId }) + .returning() + .then(takeFirst) + .then((policy) => policy.id); + const policyId = input.policyId ?? overridePolicyId; + + const env = await db + .insert(environment) + .values({ ...input, policyId }) + .returning() + .then(takeFirst); + + if (metadata != null) + await db.insert(SCHEMA.environmentMetadata).values( + Object.entries(metadata).map(([key, value]) => ({ + environmentId: env.id, + key, + value, + })), + ); + + if (releaseChannels != null && releaseChannels.length > 0) + await createReleaseChannels(db, env.id, releaseChannels); + + await db + .update(environmentPolicy) + .set({ environmentId: env.id }) + .where(eq(environmentPolicy.id, policyId)); + + return env; +}; diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts index dc132e56..c2e11263 100644 --- a/packages/db/src/index.ts +++ b/packages/db/src/index.ts @@ -6,3 +6,4 @@ export { workspaceSchema, systemSchema, } from "./schema/index.js"; +export * from "./create-env.js"; diff --git a/packages/db/src/schema/environment-policy-deployment.ts b/packages/db/src/schema/environment-policy-deployment.ts index b01acc25..9a8d8eef 100644 --- a/packages/db/src/schema/environment-policy-deployment.ts +++ b/packages/db/src/schema/environment-policy-deployment.ts @@ -2,8 +2,7 @@ import type { InferSelectModel } from "drizzle-orm"; import { pgTable, uniqueIndex, uuid } from "drizzle-orm/pg-core"; import { createInsertSchema } from "drizzle-zod"; -import { environmentPolicy } from "./environment-policy.js"; -import { environment } from "./environment.js"; +import { environment, environmentPolicy } from "./environment.js"; export const environmentPolicyDeployment = pgTable( "environment_policy_deployment", diff --git a/packages/db/src/schema/environment-policy-relations.ts b/packages/db/src/schema/environment-policy-relations.ts index 432640b3..12cf415c 100644 --- a/packages/db/src/schema/environment-policy-relations.ts +++ b/packages/db/src/schema/environment-policy-relations.ts @@ -1,7 +1,6 @@ import { relations } from "drizzle-orm"; -import { environmentPolicy } from "./environment-policy.js"; -import { environment } from "./environment.js"; +import { environment, environmentPolicy } from "./environment.js"; import { environmentPolicyReleaseChannel } from "./release-channel.js"; export const environmentPolicyRelations = relations( diff --git a/packages/db/src/schema/environment-policy.ts b/packages/db/src/schema/environment-policy.ts deleted file mode 100644 index 1edbb4d2..00000000 --- a/packages/db/src/schema/environment-policy.ts +++ /dev/null @@ -1,151 +0,0 @@ -import type { InferSelectModel } from "drizzle-orm"; -import { sql } from "drizzle-orm"; -import { - bigint, - integer, - pgEnum, - pgTable, - text, - timestamp, - uniqueIndex, - uuid, -} from "drizzle-orm/pg-core"; -import { createInsertSchema } from "drizzle-zod"; -import { z } from "zod"; - -import { user } from "./auth.js"; -import { release } from "./release.js"; -import { system } from "./system.js"; - -export const approvalRequirement = pgEnum( - "environment_policy_approval_requirement", - ["manual", "automatic"], -); - -export const environmentPolicyDeploymentSuccessType = pgEnum( - "environment_policy_deployment_success_type", - ["all", "some", "optional"], -); - -export const releaseSequencingType = pgEnum("release_sequencing_type", [ - "wait", - "cancel", -]); - -export const environmentPolicy = pgTable("environment_policy", { - id: uuid("id").primaryKey().defaultRandom(), - name: text("name").notNull(), - description: text("description"), - - systemId: uuid("system_id") - .notNull() - .references(() => system.id, { onDelete: "cascade" }), - approvalRequirement: approvalRequirement("approval_required") - .notNull() - .default("manual"), - - successType: environmentPolicyDeploymentSuccessType("success_status") - .notNull() - .default("all"), - successMinimum: integer("minimum_success").notNull().default(0), - concurrencyLimit: integer("concurrency_limit").default(sql`NULL`), - - // Duration in milliseconds over which to gradually roll out releases to this - // environment - rolloutDuration: bigint("rollout_duration", { mode: "number" }) - .notNull() - .default(0), - - // Minimum interval between releases in milliseconds - minimumReleaseInterval: bigint("minimum_release_interval", { - mode: "number", - }) - .notNull() - .default(0), - - releaseSequencing: releaseSequencingType("release_sequencing") - .notNull() - .default("cancel"), -}); - -export type EnvironmentPolicy = InferSelectModel; - -export const createEnvironmentPolicy = createInsertSchema( - environmentPolicy, -).omit({ id: true }); - -export const updateEnvironmentPolicy = createEnvironmentPolicy - .partial() - .extend({ - releaseChannels: z.record(z.string().uuid().nullable()).optional(), - releaseWindows: z - .array( - z.object({ - policyId: z.string().uuid(), - recurrence: z.enum(["hourly", "daily", "weekly", "monthly"]), - startTime: z.date(), - endTime: z.date(), - }), - ) - .optional(), - }); - -export const recurrenceType = pgEnum("recurrence_type", [ - "hourly", - "daily", - "weekly", - "monthly", -]); - -export const environmentPolicyReleaseWindow = pgTable( - "environment_policy_release_window", - { - id: uuid("id").primaryKey().defaultRandom(), - policyId: uuid("policy_id") - .notNull() - .references(() => environmentPolicy.id, { onDelete: "cascade" }), - startTime: timestamp("start_time", { - withTimezone: true, - precision: 0, - }).notNull(), - endTime: timestamp("end_time", { - withTimezone: true, - precision: 0, - }).notNull(), - recurrence: recurrenceType("recurrence").notNull(), - }, -); - -export type EnvironmentPolicyReleaseWindow = InferSelectModel< - typeof environmentPolicyReleaseWindow ->; - -export const approvalStatusType = pgEnum("approval_status_type", [ - "pending", - "approved", - "rejected", -]); - -export const environmentPolicyApproval = pgTable( - "environment_policy_approval", - { - id: uuid("id").primaryKey().defaultRandom(), - policyId: uuid("policy_id") - .notNull() - .references(() => environmentPolicy.id, { onDelete: "cascade" }), - releaseId: uuid("release_id") - .notNull() - .references(() => release.id, { onDelete: "cascade" }), - status: approvalStatusType("status").notNull().default("pending"), - userId: uuid("user_id").references(() => user.id, { onDelete: "set null" }), - approvedAt: timestamp("approved_at", { - withTimezone: true, - precision: 0, - }).default(sql`NULL`), - }, - (t) => ({ uniq: uniqueIndex().on(t.policyId, t.releaseId) }), -); - -export type EnvironmentPolicyApproval = InferSelectModel< - typeof environmentPolicyApproval ->; diff --git a/packages/db/src/schema/environment-relations.ts b/packages/db/src/schema/environment-relations.ts index 97ef840a..ac54131e 100644 --- a/packages/db/src/schema/environment-relations.ts +++ b/packages/db/src/schema/environment-relations.ts @@ -1,7 +1,10 @@ import { relations } from "drizzle-orm"; -import { environmentPolicy } from "./environment-policy.js"; -import { environment, environmentMetadata } from "./environment.js"; +import { + environment, + environmentMetadata, + environmentPolicy, +} from "./environment.js"; import { environmentReleaseChannel } from "./release-channel.js"; import { system } from "./system.js"; import { variableSetEnvironment } from "./variable-sets.js"; diff --git a/packages/db/src/schema/environment.ts b/packages/db/src/schema/environment.ts index ee2aec75..de3fad59 100644 --- a/packages/db/src/schema/environment.ts +++ b/packages/db/src/schema/environment.ts @@ -1,8 +1,13 @@ import type { ResourceCondition } from "@ctrlplane/validators/resources"; import type { InferSelectModel } from "drizzle-orm"; +import type { AnyPgColumn, ColumnsWithTable } from "drizzle-orm/pg-core"; import { sql } from "drizzle-orm"; import { + bigint, + foreignKey, + integer, jsonb, + pgEnum, pgTable, text, timestamp, @@ -17,7 +22,8 @@ import { resourceCondition, } from "@ctrlplane/validators/resources"; -import { environmentPolicy } from "./environment-policy.js"; +import { user } from "./auth.js"; +import { release } from "./release.js"; import { system } from "./system.js"; export const environment = pgTable( @@ -29,9 +35,7 @@ export const environment = pgTable( .references(() => system.id, { onDelete: "cascade" }), name: text("name").notNull(), description: text("description").default(""), - policyId: uuid("policy_id").references(() => environmentPolicy.id, { - onDelete: "set null", - }), + policyId: uuid("policy_id").notNull(), resourceFilter: jsonb("resource_filter") .$type() .default(sql`NULL`), @@ -39,7 +43,13 @@ export const environment = pgTable( .notNull() .defaultNow(), }, - (t) => ({ uniq: uniqueIndex().on(t.systemId, t.name) }), + (t) => ({ + uniq: uniqueIndex().on(t.systemId, t.name), + policyIdFk: foreignKey({ + columns: [t.policyId], + foreignColumns: [environmentPolicy.id], + }).onDelete("set null"), + }), ); export type Environment = InferSelectModel; @@ -49,7 +59,7 @@ export const createEnvironment = createInsertSchema(environment, { .optional() .refine((filter) => filter == null || isValidResourceCondition(filter)), }) - .omit({ id: true }) + .omit({ id: true, policyId: true }) .extend({ releaseChannels: z .array( @@ -65,9 +75,13 @@ export const createEnvironment = createInsertSchema(environment, { return deploymentIds.size === channels.length; }), metadata: z.record(z.string()).optional(), + policyId: z.string().uuid().optional(), }); -export const updateEnvironment = createEnvironment.partial(); +export const updateEnvironment = createEnvironment + .partial() + .omit({ policyId: true }) + .extend({ policyId: z.string().uuid().nullable().optional() }); export type InsertEnvironment = z.infer; export const environmentMetadata = pgTable( @@ -82,3 +96,158 @@ export const environmentMetadata = pgTable( }, (t) => ({ uniq: uniqueIndex().on(t.key, t.environmentId) }), ); + +export const approvalRequirement = pgEnum( + "environment_policy_approval_requirement", + ["manual", "automatic"], +); + +export const environmentPolicyDeploymentSuccessType = pgEnum( + "environment_policy_deployment_success_type", + ["all", "some", "optional"], +); + +export const releaseSequencingType = pgEnum("release_sequencing_type", [ + "wait", + "cancel", +]); + +export const environmentPolicy = pgTable( + "environment_policy", + { + id: uuid("id").primaryKey().defaultRandom(), + name: text("name").notNull(), + description: text("description"), + + systemId: uuid("system_id") + .notNull() + .references(() => system.id, { onDelete: "cascade" }), + environmentId: uuid("environment_id").references( + (): any => environment.id, + { onDelete: "cascade" }, + ), + approvalRequirement: approvalRequirement("approval_required") + .notNull() + .default("manual"), + + successType: environmentPolicyDeploymentSuccessType("success_status") + .notNull() + .default("all"), + successMinimum: integer("minimum_success").notNull().default(0), + concurrencyLimit: integer("concurrency_limit").default(sql`NULL`), + + // Duration in milliseconds over which to gradually roll out releases to this + // environment + rolloutDuration: bigint("rollout_duration", { mode: "number" }) + .notNull() + .default(0), + + // Minimum interval between releases in milliseconds + minimumReleaseInterval: bigint("minimum_release_interval", { + mode: "number", + }) + .notNull() + .default(0), + + releaseSequencing: releaseSequencingType("release_sequencing") + .notNull() + .default("cancel"), + }, + () => ({ + overridePolicyFK: foreignKey(overridePolicyFKConstraint).onDelete( + "cascade", + ), + }), +); + +export type EnvironmentPolicy = InferSelectModel; + +const overridePolicyFKConstraint: { + columns: [AnyPgColumn<{ tableName: "environment_policy" }>]; + foreignColumns: ColumnsWithTable< + "environment_policy", + "environment", + [AnyPgColumn<{ tableName: "environment_policy" }>] + >; +} = { + columns: [environmentPolicy.environmentId], + foreignColumns: [environment.id], +}; + +export const createEnvironmentPolicy = createInsertSchema(environmentPolicy) + .omit({ id: true }) + .extend({ + releaseChannels: z.record(z.string().uuid().nullable()).optional(), + releaseWindows: z + .array( + z.object({ + recurrence: z.enum(["hourly", "daily", "weekly", "monthly"]), + startTime: z.date(), + endTime: z.date(), + }), + ) + .optional(), + }); +export type CreateEnvironmentPolicy = z.infer; +export const updateEnvironmentPolicy = createEnvironmentPolicy.partial(); +export type UpdateEnvironmentPolicy = z.infer; + +export const recurrenceType = pgEnum("recurrence_type", [ + "hourly", + "daily", + "weekly", + "monthly", +]); + +export const environmentPolicyReleaseWindow = pgTable( + "environment_policy_release_window", + { + id: uuid("id").primaryKey().defaultRandom(), + policyId: uuid("policy_id") + .notNull() + .references(() => environmentPolicy.id, { onDelete: "cascade" }), + startTime: timestamp("start_time", { + withTimezone: true, + precision: 0, + }).notNull(), + endTime: timestamp("end_time", { + withTimezone: true, + precision: 0, + }).notNull(), + recurrence: recurrenceType("recurrence").notNull(), + }, +); + +export type EnvironmentPolicyReleaseWindow = InferSelectModel< + typeof environmentPolicyReleaseWindow +>; + +export const approvalStatusType = pgEnum("approval_status_type", [ + "pending", + "approved", + "rejected", +]); + +export const environmentPolicyApproval = pgTable( + "environment_policy_approval", + { + id: uuid("id").primaryKey().defaultRandom(), + policyId: uuid("policy_id") + .notNull() + .references(() => environmentPolicy.id, { onDelete: "cascade" }), + releaseId: uuid("release_id") + .notNull() + .references(() => release.id, { onDelete: "cascade" }), + status: approvalStatusType("status").notNull().default("pending"), + userId: uuid("user_id").references(() => user.id, { onDelete: "set null" }), + approvedAt: timestamp("approved_at", { + withTimezone: true, + precision: 0, + }).default(sql`NULL`), + }, + (t) => ({ uniq: uniqueIndex().on(t.policyId, t.releaseId) }), +); + +export type EnvironmentPolicyApproval = InferSelectModel< + typeof environmentPolicyApproval +>; diff --git a/packages/db/src/schema/index.ts b/packages/db/src/schema/index.ts index 707e761f..1f2425cb 100644 --- a/packages/db/src/schema/index.ts +++ b/packages/db/src/schema/index.ts @@ -3,7 +3,6 @@ export * from "./resource.js"; export * from "./resource-provider.js"; export * from "./deployment.js"; export * from "./environment.js"; -export * from "./environment-policy.js"; export * from "./environment-policy-deployment.js"; export * from "./release.js"; export * from "./system.js"; diff --git a/packages/db/src/schema/release-channel-relations.ts b/packages/db/src/schema/release-channel-relations.ts index ed6ac3ca..b001fea7 100644 --- a/packages/db/src/schema/release-channel-relations.ts +++ b/packages/db/src/schema/release-channel-relations.ts @@ -1,7 +1,6 @@ import { relations } from "drizzle-orm"; -import { environmentPolicy } from "./environment-policy.js"; -import { environment } from "./environment.js"; +import { environment, environmentPolicy } from "./environment.js"; import { environmentPolicyReleaseChannel, environmentReleaseChannel, diff --git a/packages/db/src/schema/release-channel.ts b/packages/db/src/schema/release-channel.ts index b9572dcd..51ac234b 100644 --- a/packages/db/src/schema/release-channel.ts +++ b/packages/db/src/schema/release-channel.ts @@ -2,8 +2,7 @@ import type { InferSelectModel } from "drizzle-orm"; import { pgTable, uniqueIndex, uuid } from "drizzle-orm/pg-core"; import { deployment } from "./deployment.js"; -import { environmentPolicy } from "./environment-policy.js"; -import { environment } from "./environment.js"; +import { environment, environmentPolicy } from "./environment.js"; import { releaseChannel } from "./release.js"; export const environmentPolicyReleaseChannel = pgTable( diff --git a/packages/job-dispatch/src/__test__/job-variables-deployment.test.ts b/packages/job-dispatch/src/__test__/job-variables-deployment.test.ts index b2a4012d..d0f57a9e 100644 --- a/packages/job-dispatch/src/__test__/job-variables-deployment.test.ts +++ b/packages/job-dispatch/src/__test__/job-variables-deployment.test.ts @@ -248,7 +248,7 @@ describe("job-variables-deployment", () => { name: "test", description: null, systemId: "0", - policyId: null, + policyId: "0", resourceFilter: null, createdAt: new Date(), environments: [ @@ -317,7 +317,7 @@ describe("job-variables-deployment", () => { name: "test", description: null, systemId: "0", - policyId: null, + policyId: "0", resourceFilter: null, createdAt: new Date(), environments: [ @@ -383,7 +383,7 @@ describe("job-variables-deployment", () => { name: "test", description: null, systemId: "0", - policyId: null, + policyId: "0", resourceFilter: null, createdAt: new Date(), environments: [ @@ -449,7 +449,7 @@ describe("job-variables-deployment", () => { name: "test", description: null, systemId: "0", - policyId: null, + policyId: "0", resourceFilter: null, createdAt: new Date(), environments: [ @@ -515,7 +515,7 @@ describe("job-variables-deployment", () => { name: "test", description: null, systemId: "0", - policyId: null, + policyId: "0", resourceFilter: null, createdAt: new Date(), environments: [ @@ -611,7 +611,7 @@ describe("job-variables-deployment", () => { name: "test", description: null, systemId: "0", - policyId: null, + policyId: "0", resourceFilter: null, createdAt: new Date(), environments: [ @@ -707,7 +707,7 @@ describe("job-variables-deployment", () => { name: "test", description: null, systemId: "0", - policyId: null, + policyId: "0", resourceFilter: null, createdAt: new Date(), environments: [ diff --git a/packages/job-dispatch/src/environment-creation.ts b/packages/job-dispatch/src/environment-creation.ts index b8df69f8..270a52f2 100644 --- a/packages/job-dispatch/src/environment-creation.ts +++ b/packages/job-dispatch/src/environment-creation.ts @@ -33,8 +33,7 @@ export const createJobsForNewEnvironment = async ( const { releaseChannels: envReleaseChannels, system } = releaseChannels; const { workspaceId, deployments } = system; - const policyReleaseChannels = - releaseChannels.policy?.environmentPolicyReleaseChannels ?? []; + const { environmentPolicyReleaseChannels } = releaseChannels.policy; const resources = await db .select() @@ -52,7 +51,7 @@ export const createJobsForNewEnvironment = async ( const envReleaseChannel = envReleaseChannels.find( (erc) => erc.deploymentId === deployment.id, ); - const policyReleaseChannel = policyReleaseChannels.find( + const policyReleaseChannel = environmentPolicyReleaseChannels.find( (prc) => prc.deploymentId === deployment.id, ); const { releaseFilter } = diff --git a/packages/job-dispatch/src/resource/dispatch-resource.ts b/packages/job-dispatch/src/resource/dispatch-resource.ts index 21b26b95..ee9e9aa8 100644 --- a/packages/job-dispatch/src/resource/dispatch-resource.ts +++ b/packages/job-dispatch/src/resource/dispatch-resource.ts @@ -66,12 +66,12 @@ export async function dispatchJobsForAddedResources( const { releaseChannels, policy, system } = environment; const { deployments } = system; - const policyReleaseChannels = policy?.environmentPolicyReleaseChannels ?? []; + const { environmentPolicyReleaseChannels } = policy; const deploymentsWithReleaseFilter = deployments.map((deployment) => { const envReleaseChannel = releaseChannels.find( (erc) => erc.deploymentId === deployment.id, ); - const policyReleaseChannel = policyReleaseChannels.find( + const policyReleaseChannel = environmentPolicyReleaseChannels.find( (prc) => prc.deploymentId === deployment.id, );