diff --git a/apps/event-worker/package.json b/apps/event-worker/package.json index 5392b9a0..89b68de6 100644 --- a/apps/event-worker/package.json +++ b/apps/event-worker/package.json @@ -15,6 +15,8 @@ "@aws-sdk/client-ec2": "^3.701.0", "@aws-sdk/client-eks": "^3.699.0", "@aws-sdk/client-sts": "^3.699.0", + "@azure/arm-containerservice": "^21.3.0", + "@azure/identity": "^4.5.0", "@ctrlplane/db": "workspace:*", "@ctrlplane/job-dispatch": "workspace:*", "@ctrlplane/logger": "workspace:*", diff --git a/apps/event-worker/src/config.ts b/apps/event-worker/src/config.ts index a94e2f5d..f3b20983 100644 --- a/apps/event-worker/src/config.ts +++ b/apps/event-worker/src/config.ts @@ -12,6 +12,8 @@ export const env = createEnv({ GITHUB_BOT_PRIVATE_KEY: z.string().optional(), GITHUB_BOT_CLIENT_ID: z.string().optional(), GITHUB_BOT_CLIENT_SECRET: z.string().optional(), + AZURE_APP_CLIENT_ID: z.string().optional(), + AZURE_APP_CLIENT_SECRET: z.string().optional(), }, runtimeEnv: process.env, }); diff --git a/apps/event-worker/src/index.ts b/apps/event-worker/src/index.ts index 83ce4f21..ee1249c1 100644 --- a/apps/event-worker/src/index.ts +++ b/apps/event-worker/src/index.ts @@ -2,23 +2,16 @@ import { logger } from "@ctrlplane/logger"; import { createDispatchExecutionJobWorker } from "./job-dispatch/index.js"; import { redis } from "./redis.js"; -import { - createAwsResourceScanWorker, - createGoogleResourceScanWorker, -} from "./resource-scan/index.js"; +import { createResourceScanWorker } from "./resource-scan/index.js"; -const resourceGoogleScanWorker = createGoogleResourceScanWorker(); -const resourceAwsScanWorker = createAwsResourceScanWorker(); +const resourceScanWorker = createResourceScanWorker(); const dispatchExecutionJobWorker = createDispatchExecutionJobWorker(); const shutdown = () => { logger.warn("Exiting..."); - resourceAwsScanWorker.close(); - resourceGoogleScanWorker.close(); + resourceScanWorker.close(); dispatchExecutionJobWorker.close(); - redis.quit(); - process.exit(0); }; diff --git a/apps/event-worker/src/resource-scan/aws/eks.ts b/apps/event-worker/src/resource-scan/aws/eks.ts index 77927c4c..8a52320f 100644 --- a/apps/event-worker/src/resource-scan/aws/eks.ts +++ b/apps/event-worker/src/resource-scan/aws/eks.ts @@ -146,6 +146,7 @@ export const getEksResources = async ( workspace: Workspace, config: ResourceProviderAws, ) => { + if (!config.importEks) return []; const { awsRoleArn: workspaceRoleArn } = workspace; if (workspaceRoleArn == null) return []; @@ -161,22 +162,20 @@ export const getEksResources = async ( const credentials = await assumeWorkspaceRole(workspaceRoleArn); const workspaceStsClient = credentials.sts(); - const resources = config.importEks - ? await _.chain(config.awsRoleArns) - .map((customerRoleArn) => - scanEksClustersByAssumedRole(workspaceStsClient, customerRoleArn), - ) - .thru((promises) => Promise.all(promises)) - .value() - .then((results) => results.flat()) - .then((resources) => - resources.map((resource) => ({ - ...resource, - workspaceId: workspace.id, - providerId: config.resourceProviderId, - })), - ) - : []; + const resources = await _.chain(config.awsRoleArns) + .map((customerRoleArn) => + scanEksClustersByAssumedRole(workspaceStsClient, customerRoleArn), + ) + .thru((promises) => Promise.all(promises)) + .value() + .then((results) => results.flat()) + .then((resources) => + resources.map((resource) => ({ + ...resource, + workspaceId: workspace.id, + providerId: config.resourceProviderId, + })), + ); const resourceTypes = _.countBy(resources, (resource) => [resource.kind, resource.version].join("/"), diff --git a/apps/event-worker/src/resource-scan/aws/index.ts b/apps/event-worker/src/resource-scan/aws/index.ts deleted file mode 100644 index a1a6a539..00000000 --- a/apps/event-worker/src/resource-scan/aws/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { logger } from "@ctrlplane/logger"; - -import { createResourceScanWorker } from "../utils.js"; -import { getEksResources } from "./eks.js"; -import { getVpcResources } from "./vpc.js"; - -const log = logger.child({ label: "resource-scan/aws" }); - -export const createAwsResourceScanWorker = () => - createResourceScanWorker(async (rp) => { - if (rp.resource_provider_aws == null) { - log.info( - `No AWS provider found for resource provider ${rp.resource_provider.id}, skipping scan`, - ); - return []; - } - - const eksResources = await getEksResources( - rp.workspace, - rp.resource_provider_aws, - ); - - const vpcResources = await getVpcResources( - rp.workspace, - rp.resource_provider_aws, - ); - - return [...eksResources, ...vpcResources]; - }); diff --git a/apps/event-worker/src/resource-scan/aws/vpc.ts b/apps/event-worker/src/resource-scan/aws/vpc.ts index db84ed71..3890f778 100644 --- a/apps/event-worker/src/resource-scan/aws/vpc.ts +++ b/apps/event-worker/src/resource-scan/aws/vpc.ts @@ -169,6 +169,8 @@ export const getVpcResources = async ( workspace: Workspace, config: ResourceProviderAws, ) => { + if (!config.importVpc) return []; + const { awsRoleArn: workspaceRoleArn } = workspace; if (workspaceRoleArn == null) return []; @@ -184,22 +186,20 @@ export const getVpcResources = async ( const credentials = await assumeWorkspaceRole(workspaceRoleArn); const workspaceStsClient = credentials.sts(); - const resources = config.importVpc - ? await _.chain(config.awsRoleArns) - .map((customerRoleArn) => - scanVpcsByAssumedRole(workspaceStsClient, customerRoleArn), - ) - .thru((promises) => Promise.all(promises)) - .value() - .then((results) => results.flat()) - .then((resources) => - resources.map((resource) => ({ - ...resource, - workspaceId: workspace.id, - providerId: config.resourceProviderId, - })), - ) - : []; + const resources = await _.chain(config.awsRoleArns) + .map((customerRoleArn) => + scanVpcsByAssumedRole(workspaceStsClient, customerRoleArn), + ) + .thru((promises) => Promise.all(promises)) + .value() + .then((results) => results.flat()) + .then((resources) => + resources.map((resource) => ({ + ...resource, + workspaceId: workspace.id, + providerId: config.resourceProviderId, + })), + ); log.info(`Found ${resources.length} VPC resources`); diff --git a/apps/event-worker/src/resource-scan/azure/aks.ts b/apps/event-worker/src/resource-scan/azure/aks.ts new file mode 100644 index 00000000..84406e7f --- /dev/null +++ b/apps/event-worker/src/resource-scan/azure/aks.ts @@ -0,0 +1,63 @@ +import type { ManagedCluster } from "@azure/arm-containerservice"; +import { ContainerServiceClient } from "@azure/arm-containerservice"; +import { ClientSecretCredential } from "@azure/identity"; +import { isPresent } from "ts-is-present"; + +import { eq, takeFirstOrNull } from "@ctrlplane/db"; +import { db } from "@ctrlplane/db/client"; +import * as SCHEMA from "@ctrlplane/db/schema"; +import { logger } from "@ctrlplane/logger"; + +import { env } from "../../config.js"; +import { convertManagedClusterToResource } from "./cluster-to-resource.js"; + +const log = logger.child({ label: "resource-scan/azure" }); + +const AZURE_CLIENT_ID = env.AZURE_APP_CLIENT_ID; +const AZURE_CLIENT_SECRET = env.AZURE_APP_CLIENT_SECRET; + +export const getAksResources = async ( + workspace: SCHEMA.Workspace, + azureProvider: SCHEMA.ResourceProviderAzure, +) => { + if (!AZURE_CLIENT_ID || !AZURE_CLIENT_SECRET) { + log.error("Invalid azure credentials, skipping resource scan"); + return []; + } + + const tenant = await db + .select() + .from(SCHEMA.azureTenant) + .where(eq(SCHEMA.azureTenant.id, azureProvider.tenantId)) + .then(takeFirstOrNull); + + if (!tenant) { + log.error("Tenant not found, skipping resource scan"); + return []; + } + + const credential = new ClientSecretCredential( + tenant.tenantId, + AZURE_CLIENT_ID, + AZURE_CLIENT_SECRET, + ); + + const client = new ContainerServiceClient( + credential, + azureProvider.subscriptionId, + ); + + const res = client.managedClusters.list(); + + const clusters: ManagedCluster[] = []; + for await (const cluster of res) { + clusters.push(cluster); + } + + const { resourceProviderId: providerId } = azureProvider; + return clusters + .map((cluster) => + convertManagedClusterToResource(workspace.id, providerId, cluster), + ) + .filter(isPresent); +}; diff --git a/apps/event-worker/src/resource-scan/azure/cluster-to-resource.ts b/apps/event-worker/src/resource-scan/azure/cluster-to-resource.ts new file mode 100644 index 00000000..9b41ac54 --- /dev/null +++ b/apps/event-worker/src/resource-scan/azure/cluster-to-resource.ts @@ -0,0 +1,172 @@ +import type { ManagedCluster } from "@azure/arm-containerservice"; +import type { KubernetesClusterAPIV1 } from "@ctrlplane/validators/resources"; + +import { logger } from "@ctrlplane/logger"; +import { ReservedMetadataKey } from "@ctrlplane/validators/conditions"; + +import { omitNullUndefined } from "../../utils.js"; + +const log = logger.child({ module: "resource-scan/azure/cluster-to-resource" }); + +type ClusterResource = KubernetesClusterAPIV1 & { + workspaceId: string; + providerId: string; +}; + +export const convertManagedClusterToResource = ( + workspaceId: string, + providerId: string, + cluster: ManagedCluster, +): ClusterResource | null => { + if (!cluster.name || !cluster.id) { + log.error("Invalid cluster", { cluster }); + return null; + } + + return { + workspaceId, + providerId, + name: cluster.name, + identifier: cluster.id, + version: "kubernetes/v1", + kind: "ClusterAPI", + config: { + name: cluster.name, + auth: { + method: "azure/aks", + clusterName: cluster.name, + resourceGroup: cluster.nodeResourceGroup ?? "", + }, + status: cluster.provisioningState ?? "UNKNOWN", + server: { + endpoint: cluster.fqdn ?? "", + certificateAuthorityData: cluster.servicePrincipalProfile?.clientId, + }, + }, + metadata: omitNullUndefined({ + [ReservedMetadataKey.Links]: cluster.azurePortalFqdn + ? JSON.stringify({ "Azure Portal": cluster.azurePortalFqdn }) + : undefined, + [ReservedMetadataKey.ExternalId]: cluster.id ?? "", + [ReservedMetadataKey.KubernetesFlavor]: "aks", + [ReservedMetadataKey.KubernetesVersion]: cluster.currentKubernetesVersion, + + "azure/self-link": cluster.id, + "azure/resource-group": cluster.nodeResourceGroup, + "azure/location": cluster.location, + "azure/platform-version": cluster.kubernetesVersion, + "azure/enable-rbac": cluster.enableRbac, + "azure/enable-oidc": cluster.oidcIssuerProfile?.enabled, + "azure/enable-disk-csi-driver": + cluster.storageProfile?.diskCSIDriver?.enabled, + "azure/enable-file-csi-driver": + cluster.storageProfile?.fileCSIDriver?.enabled, + "azure/enable-snapshot-controller": + cluster.storageProfile?.snapshotController?.enabled, + "azure/service-principal-profile/client-id": + cluster.servicePrincipalProfile?.clientId, + "azure/oidc-issuer-profile/enabled": cluster.oidcIssuerProfile?.enabled, + "azure/oidc-issuer-profile/issuer-url": + cluster.oidcIssuerProfile?.issuerURL, + "azure/storage-profile/disk-csi-driver/enabled": + cluster.storageProfile?.diskCSIDriver?.enabled, + "azure/storage-profile/file-csi-driver/enabled": + cluster.storageProfile?.fileCSIDriver?.enabled, + "azure/storage-profile/snapshot-controller/enabled": + cluster.storageProfile?.snapshotController?.enabled, + "azure/network-profile/network-plugin": + cluster.networkProfile?.networkPlugin, + "azure/network-profile/network-policy": + cluster.networkProfile?.networkPolicy, + "azure/network-profile/pod-cidr": cluster.networkProfile?.podCidr, + "azure/network-profile/service-cidr": cluster.networkProfile?.serviceCidr, + "azure/network-profile/dns-service-ip": + cluster.networkProfile?.dnsServiceIP, + "azure/network-profile/load-balancer-sku": + cluster.networkProfile?.loadBalancerSku, + "azure/network-profile/outbound-type": + cluster.networkProfile?.outboundType, + "azure/addon-profiles/http-application-routing/enabled": + cluster.addonProfiles?.httpApplicationRouting?.enabled, + "azure/addon-profiles/monitoring/enabled": + cluster.addonProfiles?.omsagent?.enabled, + "azure/addon-profiles/azure-policy/enabled": + cluster.addonProfiles?.azurepolicy?.enabled, + "azure/addon-profiles/ingress-application-gateway/enabled": + cluster.addonProfiles?.ingressApplicationGateway?.enabled, + "azure/addon-profiles/open-service-mesh/enabled": + cluster.addonProfiles?.openServiceMesh?.enabled, + "azure/identity/type": cluster.identity?.type, + "azure/provisioning-state": cluster.provisioningState, + "azure/power-state": cluster.powerState?.code, + "azure/max-agent-pools": String(cluster.maxAgentPools), + "azure/dns-prefix": cluster.dnsPrefix, + "azure/fqdn": cluster.fqdn, + "azure/portal-fqdn": cluster.azurePortalFqdn, + "azure/support-plan": cluster.supportPlan, + "azure/disable-local-accounts": cluster.disableLocalAccounts, + "azure/auto-upgrade-profile/channel": + cluster.autoUpgradeProfile?.upgradeChannel, + "azure/sku/name": cluster.sku?.name, + "azure/sku/tier": cluster.sku?.tier, + "azure/agent-pool-profiles": JSON.stringify(cluster.agentPoolProfiles), + "azure/windows-profile/admin-username": + cluster.windowsProfile?.adminUsername, + "azure/windows-profile/enable-csi-proxy": + cluster.windowsProfile?.enableCSIProxy, + "azure/addon-profiles/azure-policy/identity/resource-id": + cluster.addonProfiles?.azurepolicy?.identity?.resourceId, + "azure/addon-profiles/azure-policy/identity/client-id": + cluster.addonProfiles?.azurepolicy?.identity?.clientId, + "azure/addon-profiles/azure-policy/identity/object-id": + cluster.addonProfiles?.azurepolicy?.identity?.objectId, + "azure/addon-profiles/ingress-application-gateway/config/application-gateway-id": + cluster.addonProfiles?.ingressApplicationGateway?.config + ?.applicationGatewayId, + "azure/addon-profiles/ingress-application-gateway/config/effective-application-gateway-id": + cluster.addonProfiles?.ingressApplicationGateway?.config + ?.effectiveApplicationGatewayId, + "azure/addon-profiles/ingress-application-gateway/identity/resource-id": + cluster.addonProfiles?.ingressApplicationGateway?.identity?.resourceId, + "azure/addon-profiles/ingress-application-gateway/identity/client-id": + cluster.addonProfiles?.ingressApplicationGateway?.identity?.clientId, + "azure/addon-profiles/ingress-application-gateway/identity/object-id": + cluster.addonProfiles?.ingressApplicationGateway?.identity?.objectId, + "azure/network-profile/network-dataplane": + cluster.networkProfile?.networkDataplane, + "azure/network-profile/load-balancer-profile/managed-outbound-ips/count": + cluster.networkProfile?.loadBalancerProfile?.managedOutboundIPs?.count, + "azure/network-profile/load-balancer-profile/managed-outbound-ips/count-ipv6": + cluster.networkProfile?.loadBalancerProfile?.managedOutboundIPs + ?.countIPv6, + "azure/network-profile/load-balancer-profile/effective-outbound-ips": + JSON.stringify( + cluster.networkProfile?.loadBalancerProfile?.effectiveOutboundIPs, + ), + "azure/network-profile/load-balancer-profile/allocated-outbound-ports": + cluster.networkProfile?.loadBalancerProfile?.allocatedOutboundPorts, + "azure/network-profile/load-balancer-profile/idle-timeout-in-minutes": + cluster.networkProfile?.loadBalancerProfile?.idleTimeoutInMinutes, + "azure/network-profile/load-balancer-profile/backend-pool-type": + cluster.networkProfile?.loadBalancerProfile?.backendPoolType, + "azure/network-profile/service-cidrs": JSON.stringify( + cluster.networkProfile?.serviceCidrs, + ), + "azure/network-profile/ip-families": JSON.stringify( + cluster.networkProfile?.ipFamilies, + ), + "azure/identity-profile/kubeletidentity/resource-id": + cluster.identityProfile?.kubeletidentity?.resourceId, + "azure/identity-profile/kubeletidentity/client-id": + cluster.identityProfile?.kubeletidentity?.clientId, + "azure/identity-profile/kubeletidentity/object-id": + cluster.identityProfile?.kubeletidentity?.objectId, + "azure/security-profile/defender/enable-defender": + cluster.securityProfile?.defender?.securityMonitoring?.enabled, + "azure/security-profile/defender/log-analytics-workspace-id": + cluster.securityProfile?.defender?.logAnalyticsWorkspaceResourceId, + "azure/security-profile/defender/security-monitoring/enabled": + cluster.securityProfile?.defender?.securityMonitoring?.enabled, + }), + }; +}; diff --git a/apps/event-worker/src/resource-scan/google/index.ts b/apps/event-worker/src/resource-scan/google/index.ts deleted file mode 100644 index 5222bd13..00000000 --- a/apps/event-worker/src/resource-scan/google/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { logger } from "@ctrlplane/logger"; - -import { createResourceScanWorker } from "../utils.js"; -import { getGkeResources } from "./gke.js"; - -const log = logger.child({ label: "resource-scan/google" }); - -export const createGoogleResourceScanWorker = () => - createResourceScanWorker(async (rp) => { - if (rp.resource_provider_google == null) { - log.info( - `No Google provider found for resource provider ${rp.resource_provider.id}, skipping scan`, - ); - return []; - } - - const resources = await getGkeResources( - rp.workspace, - rp.resource_provider_google, - ); - - return resources; - }); diff --git a/apps/event-worker/src/resource-scan/index.ts b/apps/event-worker/src/resource-scan/index.ts index ff55bcb6..37cf903f 100644 --- a/apps/event-worker/src/resource-scan/index.ts +++ b/apps/event-worker/src/resource-scan/index.ts @@ -1,2 +1,119 @@ -export { createGoogleResourceScanWorker } from "./google/index.js"; -export { createAwsResourceScanWorker } from "./aws/index.js"; +import type { ResourceScanEvent } from "@ctrlplane/validators/events"; +import type { Job } from "bullmq"; +import { Queue, Worker } from "bullmq"; + +import { eq, takeFirstOrNull } from "@ctrlplane/db"; +import { db } from "@ctrlplane/db/client"; +import { + resourceProvider, + resourceProviderAws, + resourceProviderAzure, + resourceProviderGoogle, + workspace, +} from "@ctrlplane/db/schema"; +import { upsertResources } from "@ctrlplane/job-dispatch"; +import { logger } from "@ctrlplane/logger"; +import { Channel } from "@ctrlplane/validators/events"; + +import { redis } from "../redis.js"; +import { getEksResources } from "./aws/eks.js"; +import { getVpcResources as getAwsVpcResources } from "./aws/vpc.js"; +import { getAksResources } from "./azure/aks.js"; +import { getGkeResources } from "./google/gke.js"; +import { getVpcResources as getGoogleVpcResources } from "./google/vpc.js"; + +const log = logger.child({ label: "resource-scan" }); + +const resourceScanQueue = new Queue(Channel.ResourceScan, { + connection: redis, +}); + +const removeResourceJob = (job: Job) => + job.repeatJobKey != null + ? resourceScanQueue.removeRepeatableByKey(job.repeatJobKey) + : null; + +const getResources = async (rp: any) => { + if (rp.resource_provider_google != null) { + const [gkeResources, vpcResources] = await Promise.all([ + getGkeResources(rp.workspace, rp.resource_provider_google), + getGoogleVpcResources(rp.workspace, rp.resource_provider_google), + ]); + return [...gkeResources, ...vpcResources]; + } + + if (rp.resource_provider_aws != null) { + const [eksResources, vpcResources] = await Promise.all([ + getEksResources(rp.workspace, rp.resource_provider_aws), + getAwsVpcResources(rp.workspace, rp.resource_provider_aws), + ]); + return [...eksResources, ...vpcResources]; + } + + if (rp.resource_provider_azure != null) + return getAksResources(rp.workspace, rp.resource_provider_azure); + throw new Error("Invalid resource provider"); +}; + +export const createResourceScanWorker = () => + new Worker( + Channel.ResourceScan, + async (job) => { + const { resourceProviderId } = job.data; + + const rp = await db + .select() + .from(resourceProvider) + .where(eq(resourceProvider.id, resourceProviderId)) + .innerJoin(workspace, eq(resourceProvider.workspaceId, workspace.id)) + .leftJoin( + resourceProviderGoogle, + eq(resourceProvider.id, resourceProviderGoogle.resourceProviderId), + ) + .leftJoin( + resourceProviderAws, + eq(resourceProvider.id, resourceProviderAws.resourceProviderId), + ) + .leftJoin( + resourceProviderAzure, + eq(resourceProvider.id, resourceProviderAzure.resourceProviderId), + ) + .then(takeFirstOrNull); + + if (rp == null) { + log.error(`Resource provider with ID ${resourceProviderId} not found.`); + await removeResourceJob(job); + return; + } + + log.info( + `Received scanning request for "${rp.resource_provider.name}" (${resourceProviderId}).`, + ); + + try { + const resources = await getResources(rp); + if (resources.length === 0) { + log.info( + `No resources found for provider ${rp.resource_provider.id}, skipping upsert.`, + ); + return; + } + + log.info( + `Upserting ${resources.length} resources for provider ${rp.resource_provider.id}`, + ); + await upsertResources(db, resources); + } catch (error: any) { + log.error( + `Error scanning/upserting resources for provider ${rp.resource_provider.id}: ${error.message}`, + { error }, + ); + } + }, + { + connection: redis, + removeOnComplete: { age: 1 * 60 * 60, count: 5000 }, + removeOnFail: { age: 12 * 60 * 60, count: 5000 }, + concurrency: 10, + }, + ); diff --git a/apps/event-worker/src/resource-scan/utils.ts b/apps/event-worker/src/resource-scan/utils.ts deleted file mode 100644 index d07b096b..00000000 --- a/apps/event-worker/src/resource-scan/utils.ts +++ /dev/null @@ -1,91 +0,0 @@ -import type { InsertResource } from "@ctrlplane/db/schema"; -import type { ResourceScanEvent } from "@ctrlplane/validators/events"; -import type { Job } from "bullmq"; -import { Queue, Worker } from "bullmq"; - -import { eq, takeFirstOrNull } from "@ctrlplane/db"; -import { db } from "@ctrlplane/db/client"; -import { - resourceProvider, - resourceProviderAws, - resourceProviderGoogle, - workspace, -} from "@ctrlplane/db/schema"; -import { upsertResources } from "@ctrlplane/job-dispatch"; -import { logger } from "@ctrlplane/logger"; -import { Channel } from "@ctrlplane/validators/events"; - -import { redis } from "../redis.js"; - -const log = logger.child({ label: "resource-scan" }); - -const resourceScanQueue = new Queue(Channel.ResourceScan, { - connection: redis, -}); - -const removeResourceJob = (job: Job) => - job.repeatJobKey != null - ? resourceScanQueue.removeRepeatableByKey(job.repeatJobKey) - : null; - -export const createResourceScanWorker = ( - scanResources: (rp: any) => Promise, -) => - new Worker( - Channel.ResourceScan, - async (job) => { - const { resourceProviderId } = job.data; - - const rp = await db - .select() - .from(resourceProvider) - .where(eq(resourceProvider.id, resourceProviderId)) - .innerJoin(workspace, eq(resourceProvider.workspaceId, workspace.id)) - .leftJoin( - resourceProviderGoogle, - eq(resourceProvider.id, resourceProviderGoogle.resourceProviderId), - ) - .leftJoin( - resourceProviderAws, - eq(resourceProvider.id, resourceProviderAws.resourceProviderId), - ) - .then(takeFirstOrNull); - - if (rp == null) { - log.error(`Resource provider with ID ${resourceProviderId} not found.`); - await removeResourceJob(job); - return; - } - - log.info( - `Received scanning request for "${rp.resource_provider.name}" (${resourceProviderId}).`, - ); - - try { - const resources = await scanResources(rp); - - log.info( - `Upserting ${resources.length} resources for provider ${rp.resource_provider.id}`, - ); - - if (resources.length > 0) { - await upsertResources(db, resources); - } else { - log.info( - `No resources found for provider ${rp.resource_provider.id}, skipping upsert.`, - ); - } - } catch (error: any) { - log.error( - `Error scanning/upserting resources for provider ${rp.resource_provider.id}: ${error.message}`, - { error }, - ); - } - }, - { - connection: redis, - removeOnComplete: { age: 1 * 60 * 60, count: 5000 }, - removeOnFail: { age: 12 * 60 * 60, count: 5000 }, - concurrency: 10, - }, - ); diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/(resources)/resource-providers/ProviderActionsDropdown.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/(resources)/resource-providers/ProviderActionsDropdown.tsx index b65a7a1a..a87e1ae3 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/(resources)/resource-providers/ProviderActionsDropdown.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/(resources)/resource-providers/ProviderActionsDropdown.tsx @@ -1,10 +1,6 @@ "use client"; -import type { - ResourceProvider, - ResourceProviderAws, - ResourceProviderGoogle, -} from "@ctrlplane/db/schema"; +import type { RouterOutputs } from "@ctrlplane/api"; import { useState } from "react"; import { useRouter } from "next/navigation"; import { IconDots } from "@tabler/icons-react"; @@ -30,12 +26,10 @@ import { import { api } from "~/trpc/react"; import { UpdateAwsProviderDialog } from "./integrations/aws/UpdateAwsProviderDialog"; +import { UpdateAzureProviderDialog } from "./integrations/azure/UpdateAzureProviderDialog"; import { UpdateGoogleProviderDialog } from "./integrations/google/UpdateGoogleProviderDialog"; -type Provider = ResourceProvider & { - googleConfig: ResourceProviderGoogle | null; - awsConfig: ResourceProviderAws | null; -}; +type Provider = RouterOutputs["resource"]["provider"]["byWorkspaceId"][number]; export const ProviderActionsDropdown: React.FC<{ provider: Provider; @@ -43,7 +37,9 @@ export const ProviderActionsDropdown: React.FC<{ const [open, setOpen] = useState(false); const utils = api.useUtils(); const isManagedProvider = - provider.googleConfig != null || provider.awsConfig != null; + provider.googleConfig != null || + provider.awsConfig != null || + provider.azureConfig != null; const deleteProvider = api.resource.provider.delete.useMutation({ onSuccess: () => utils.resource.provider.byWorkspaceId.invalidate(), @@ -92,6 +88,18 @@ export const ProviderActionsDropdown: React.FC<{ )} + {provider.azureConfig != null && ( + setOpen(false)} + > + e.preventDefault()}> + Edit + + + )} {isManagedProvider && ( { diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/(resources)/resource-providers/integrations/azure/CreateAzureProviderDialog.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/(resources)/resource-providers/integrations/azure/CreateAzureProviderDialog.tsx new file mode 100644 index 00000000..8296f619 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/(resources)/resource-providers/integrations/azure/CreateAzureProviderDialog.tsx @@ -0,0 +1,104 @@ +"use client"; + +import { useRouter } from "next/navigation"; + +import { createResourceProviderAzure } from "@ctrlplane/db/schema"; +import { Button } from "@ctrlplane/ui/button"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@ctrlplane/ui/dialog"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, + useForm, +} from "@ctrlplane/ui/form"; +import { Input } from "@ctrlplane/ui/input"; + +type CreateAzureProviderDialogProps = { workspaceId: string }; + +export const CreateAzureProviderDialog: React.FC< + CreateAzureProviderDialogProps +> = ({ workspaceId }) => { + const form = useForm({ schema: createResourceProviderAzure }); + const router = useRouter(); + + const onSubmit = form.handleSubmit((data) => + router.push( + `/api/azure/${workspaceId}/${encodeURIComponent(data.tenantId)}/${encodeURIComponent(data.subscriptionId)}/${encodeURIComponent(data.name)}`, + ), + ); + + return ( + + + + + + + Configure Azure + +
+ + ( + + Name + + + + + + )} + /> + + ( + + Tenant ID + + + + + + )} + /> + + ( + + Subscription ID + + + + + )} + /> + + + + +
+
+ ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/(resources)/resource-providers/integrations/azure/UpdateAzureProviderDialog.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/(resources)/resource-providers/integrations/azure/UpdateAzureProviderDialog.tsx new file mode 100644 index 00000000..138c62b9 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/(resources)/resource-providers/integrations/azure/UpdateAzureProviderDialog.tsx @@ -0,0 +1,124 @@ +"use client"; + +import type { UpdateResourceProviderAzure } from "@ctrlplane/db/schema"; +import type * as SCHEMA from "@ctrlplane/db/schema"; +import { useState } from "react"; +import { useRouter } from "next/navigation"; + +import { updateResourceProviderAzure } from "@ctrlplane/db/schema"; +import { Button } from "@ctrlplane/ui/button"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@ctrlplane/ui/dialog"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, + useForm, +} from "@ctrlplane/ui/form"; +import { Input } from "@ctrlplane/ui/input"; + +type UpdateAzureProviderDialogProps = { + workspaceId: string; + resourceProvider: SCHEMA.ResourceProvider; + azureConfig: SCHEMA.ResourceProviderAzure; + children: React.ReactNode; + onClose?: () => void; +}; + +export const UpdateAzureProviderDialog: React.FC< + UpdateAzureProviderDialogProps +> = ({ workspaceId, resourceProvider, azureConfig, children, onClose }) => { + const [open, setOpen] = useState(false); + const form = useForm({ + schema: updateResourceProviderAzure, + defaultValues: { ...azureConfig, ...resourceProvider }, + }); + const router = useRouter(); + + const onSubmit = form.handleSubmit((data: UpdateResourceProviderAzure) => { + setOpen(false); + router.push( + `/api/azure/${workspaceId}/${encodeURIComponent(data.tenantId ?? azureConfig.tenantId)}/${encodeURIComponent(data.subscriptionId ?? azureConfig.subscriptionId)}/${encodeURIComponent(data.name ?? resourceProvider.name)}?resourceProviderId=${resourceProvider.id}`, + ); + }); + + return ( + { + setOpen(o); + if (!o) onClose?.(); + }} + > + {children} + + + Configure Azure + +
+ + ( + + Name + + + + + + )} + /> + + ( + + Tenant ID + + + + + + )} + /> + + ( + + Subscription ID + + + + + )} + /> + + + + +
+
+ ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/(resources)/resource-providers/integrations/azure/[resourceProviderId]/PermissionsButton.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/(resources)/resource-providers/integrations/azure/[resourceProviderId]/PermissionsButton.tsx new file mode 100644 index 00000000..136900a0 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/(resources)/resource-providers/integrations/azure/[resourceProviderId]/PermissionsButton.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { useParams, useRouter } from "next/navigation"; + +import { Button } from "@ctrlplane/ui/button"; + +import { api } from "~/trpc/react"; + +type PermissionsButtonProps = { + resourceProviderId: string; +}; + +export const PermissionsButton: React.FC = ({ + resourceProviderId, +}) => { + const router = useRouter(); + const sync = api.resource.provider.managed.sync.useMutation(); + const { workspaceSlug } = useParams<{ workspaceSlug: string }>(); + + const handleClick = async () => { + await sync + .mutateAsync(resourceProviderId) + .then(() => router.push(`/${workspaceSlug}/resource-providers`)); + }; + + return ( + + ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/(resources)/resource-providers/integrations/azure/[resourceProviderId]/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/(resources)/resource-providers/integrations/azure/[resourceProviderId]/page.tsx new file mode 100644 index 00000000..13565fc4 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/(resources)/resource-providers/integrations/azure/[resourceProviderId]/page.tsx @@ -0,0 +1,100 @@ +import Link from "next/link"; +import { notFound } from "next/navigation"; +import { IconBrandAzure, IconExternalLink } from "@tabler/icons-react"; + +import { cn } from "@ctrlplane/ui"; +import { buttonVariants } from "@ctrlplane/ui/button"; + +import { env } from "~/env"; +import { api } from "~/trpc/server"; +import { PermissionsButton } from "./PermissionsButton"; + +type Params = { resourceProviderId: string }; + +const azureAppClientId = env.AZURE_APP_CLIENT_ID; + +export default async function AzureProviderPage({ + params, +}: { + params: Params; +}) { + if (azureAppClientId == null) return notFound(); + + const { resourceProviderId } = params; + + const provider = + await api.resource.provider.managed.azure.byProviderId(resourceProviderId); + if (provider == null) return notFound(); + + const portalUrl = `https://portal.azure.com/#@${provider.azure_tenant.tenantId}/resource/subscriptions/${provider.resource_provider_azure.subscriptionId}/users`; + const applicationPortalUrl = `https://portal.azure.com/#@${provider.azure_tenant.tenantId}/blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/Overview/appId/${azureAppClientId}`; + + return ( +
+
+

+ Next steps +

+

+ To allow Ctrlplane to scan your Azure resources, you need to grant the + Azure service principal the necessary permissions. +

+
+ +
+
+

+ Step 1: Go to Access Control (IAM) in the Azure portal +

+ + Go to IAM + +
+

+ Step 2: Click "Add role assignment" +

+
+

+ Step 3: Configure the role assignment +

+

+ a. In the "Role" tab, select "Reader" +

+

+ b. In the "Members" tab, first make sure "User, group, or service + principal" is selected. +

+

+ c. Click "Select members" and search for the application you + consented to by name. It can be found on this{" "} + + page + + , listed as the "Display name". +

+

+ d. In the "Review + assign", confirm the assignment, then click + "Review + assign" at the bottom. +

+
+

+ Step 4: Once assigned, click "Permissions Granted" +

+ +
+
+ ); +} diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/(resources)/resource-providers/integrations/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/(resources)/resource-providers/integrations/page.tsx index 88badb5c..2e098fbf 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/(resources)/resource-providers/integrations/page.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/(resources)/resource-providers/integrations/page.tsx @@ -7,14 +7,16 @@ import { SiKubernetes, SiTerraform, } from "@icons-pack/react-simple-icons"; -import { IconSettings } from "@tabler/icons-react"; +import { IconBrandAzure, IconSettings } from "@tabler/icons-react"; import { cn } from "@ctrlplane/ui"; import { Button } from "@ctrlplane/ui/button"; import { Card } from "@ctrlplane/ui/card"; +import { env } from "~/env"; import { api } from "~/trpc/server"; import { AwsActionButton } from "./AwsActionButton"; +import { CreateAzureProviderDialog } from "./azure/CreateAzureProviderDialog"; import { GoogleActionButton } from "./GoogleActionButton"; export const metadata: Metadata = { @@ -72,6 +74,7 @@ const ResourceProviders: React.FC<{ workspaceSlug: string }> = async ({ }) => { const workspace = await api.workspace.bySlug(workspaceSlug); if (workspace == null) return notFound(); + const azureAppClientId = env.AZURE_APP_CLIENT_ID; return (
@@ -117,6 +120,26 @@ const ResourceProviders: React.FC<{ workspaceSlug: string }> = async ({ + + {azureAppClientId != null && ( + + + + +
Azure
+
+

+ Grant our Azure application permissions and we will manage + running the resource provider for you. +

+ + + +
+ + +
+ )}
diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/(resources)/resource-providers/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/(resources)/resource-providers/page.tsx index 74c6f04d..abf6ad92 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/(resources)/resource-providers/page.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/(resources)/resource-providers/page.tsx @@ -1,11 +1,17 @@ +import type { RouterOutputs } from "@ctrlplane/api"; import type { ResourceCondition } from "@ctrlplane/validators/resources"; import type { Metadata } from "next"; import Link from "next/link"; import { notFound } from "next/navigation"; -import { IconExternalLink, IconSettings } from "@tabler/icons-react"; -import { formatDistanceToNow } from "date-fns"; +import { SiAmazon, SiGooglecloud } from "@icons-pack/react-simple-icons"; +import { + IconBrandAzure, + IconExternalLink, + IconSettings, +} from "@tabler/icons-react"; import LZString from "lz-string"; +import { cn } from "@ctrlplane/ui"; import { Badge } from "@ctrlplane/ui/badge"; import { Table, @@ -27,9 +33,67 @@ import { api } from "~/trpc/server"; import { ProviderActionsDropdown } from "./ProviderActionsDropdown"; import { ResourceProvidersGettingStarted } from "./ResourceProvidersGettingStarted"; -export const metadata: Metadata = { - title: "Resource Providers | Ctrlplane", -}; +export const metadata: Metadata = { title: "Resource Providers | Ctrlplane" }; +type ResourceProvider = + RouterOutputs["resource"]["provider"]["byWorkspaceId"][number]; + +const isCustomProvider = (provider: ResourceProvider) => + provider.googleConfig == null && + provider.awsConfig == null && + provider.azureConfig == null; + +const CustomProviderTooltipBadge: React.FC = () => ( + + + + + Custom + + + + A custom provider is when you are running your own agent instead of + using managed agents built inside Ctrlplane. Your agent directly calls + Ctrlplane's API to create resources. + + + +); + +const ManagedProviderBadge: React.FC<{ + provider: ResourceProvider; +}> = ({ provider }) => ( + + {provider.googleConfig != null && ( + <> + + Google + + )} + {provider.awsConfig != null && ( + <> + + AWS + + )} + {provider.azureConfig != null && ( + <> + + Azure + + )} + +); export default async function ResourceProvidersPage({ params, @@ -59,7 +123,7 @@ export default async function ResourceProvidersPage({ }); return ( -
+
@@ -76,30 +140,13 @@ export default async function ResourceProvidersPage({ > -
+
{provider.name} - {provider.googleConfig == null && - provider.awsConfig == null && ( - - - - - Custom - - - - A custom provider is when you are running your own - agent instead of using managed agents built inside - Ctrlplane. Your agent directly calls Ctrlplane's - API to create resources. - - - - )} - + {isCustomProvider(provider) ? ( + + ) : ( + + )} - {provider.kinds.length > 0 ? ( - provider.kinds.map((kind) => ( - - {kind.version}:{kind.kind} - - )) - ) : ( + {provider.kinds.length === 0 && ( No resources )} + {provider.kinds.length > 0 && ( +
+ {provider.kinds.map((kind) => ( + + {kind.version}:{kind.kind} + + ))} +
+ )}
- - - - - - {new Date(provider.createdAt).toLocaleDateString()} - - - - {formatDistanceToNow(new Date(provider.createdAt), { - addSuffix: true, - })} - - - + + {new Date(provider.createdAt).toLocaleDateString()} diff --git a/apps/webservice/src/app/api/azure/[workspaceId]/[tenantId]/[subscriptionId]/[name]/route.ts b/apps/webservice/src/app/api/azure/[workspaceId]/[tenantId]/[subscriptionId]/[name]/route.ts new file mode 100644 index 00000000..fd46950b --- /dev/null +++ b/apps/webservice/src/app/api/azure/[workspaceId]/[tenantId]/[subscriptionId]/[name]/route.ts @@ -0,0 +1,150 @@ +import { randomUUID } from "crypto"; +import type { Tx } from "@ctrlplane/db"; +import type { ResourceScanEvent } from "@ctrlplane/validators/events"; +import type { NextRequest } from "next/server"; +import { NextResponse } from "next/server"; +import { Queue } from "bullmq"; +import { FORBIDDEN, INTERNAL_SERVER_ERROR, NOT_FOUND } from "http-status"; +import IORedis from "ioredis"; +import ms from "ms"; + +import { eq, takeFirstOrNull } from "@ctrlplane/db"; +import { db } from "@ctrlplane/db/client"; +import * as SCHEMA from "@ctrlplane/db/schema"; +import { logger } from "@ctrlplane/logger"; +import { Channel } from "@ctrlplane/validators/events"; + +import { env } from "~/env"; + +type Params = { + workspaceId: string; + tenantId: string; + subscriptionId: string; + name: string; +}; + +const baseUrl = env.BASE_URL; +const clientId = env.AZURE_APP_CLIENT_ID; + +const redis = new IORedis(env.REDIS_URL, { maxRetriesPerRequest: null }); +const resourceScanQueue = new Queue(Channel.ResourceScan, { + connection: redis, +}); + +const createResourceProvider = async ( + db: Tx, + workspaceId: string, + tenantId: string, + subscriptionId: string, + name: string, +) => { + const resourceProvider = await db + .insert(SCHEMA.resourceProvider) + .values({ workspaceId, name }) + .returning() + .then(takeFirstOrNull); + + if (resourceProvider == null) + throw new Error("Failed to create resource provider"); + + await db.insert(SCHEMA.resourceProviderAzure).values({ + resourceProviderId: resourceProvider.id, + tenantId, + subscriptionId, + }); + + await resourceScanQueue.add( + resourceProvider.id, + { resourceProviderId: resourceProvider.id }, + { repeat: { every: ms("10m"), immediately: true } }, + ); +}; + +export const GET = async ( + request: NextRequest, + { params }: { params: Params }, +) => { + const { workspaceId, tenantId, subscriptionId, name } = params; + const { searchParams } = new URL(request.url); + const resourceProviderId = searchParams.get("resourceProviderId"); + + return db.transaction(async (db) => { + const workspace = await db + .select() + .from(SCHEMA.workspace) + .where(eq(SCHEMA.workspace.id, workspaceId)) + .then(takeFirstOrNull); + + if (workspace == null) + return NextResponse.json( + { error: "Workspace not found" }, + { status: NOT_FOUND }, + ); + + const tenant = await db + .select() + .from(SCHEMA.azureTenant) + .where(eq(SCHEMA.azureTenant.tenantId, tenantId)) + .then(takeFirstOrNull); + + if (tenant == null) { + const state = randomUUID(); + const config = { workspaceId, tenantId, subscriptionId, name }; + const configJSON = JSON.stringify(config); + await redis.set(`azure_consent_state:${state}`, configJSON, "EX", 900); + const redirectUrl = `${baseUrl}/api/azure/consent?state=${state}`; + const consentUrlExtension = + resourceProviderId == null + ? "" + : `&resourceProviderId=${resourceProviderId}`; + const consentUrl = `https://login.microsoftonline.com/${tenantId}/adminconsent?client_id=${clientId}&redirect_uri=${redirectUrl}${consentUrlExtension}`; + return NextResponse.redirect(consentUrl); + } + + if (tenant.workspaceId !== workspaceId) + return NextResponse.json( + { error: "Tenant does not belong to this workspace" }, + { status: FORBIDDEN }, + ); + + const nextStepsUrl = `${baseUrl}/${workspace.slug}/resource-providers/integrations/azure/${resourceProviderId}`; + + if (resourceProviderId != null) + return db + .update(SCHEMA.resourceProviderAzure) + .set({ tenantId: tenant.id, subscriptionId }) + .where( + eq( + SCHEMA.resourceProviderAzure.resourceProviderId, + resourceProviderId, + ), + ) + .then(() => + resourceScanQueue.add(resourceProviderId, { resourceProviderId }), + ) + .then(() => NextResponse.redirect(nextStepsUrl)) + .catch((error) => { + logger.error(error); + return NextResponse.json( + { error: "Failed to update resource provider" }, + { status: INTERNAL_SERVER_ERROR }, + ); + }); + + return createResourceProvider( + db, + workspaceId, + tenant.id, + subscriptionId, + name, + ) + .then(() => NextResponse.redirect(nextStepsUrl)) + .catch((error) => { + logger.error(error); + return NextResponse.json( + { error: "Failed to create resource provider" }, + { status: INTERNAL_SERVER_ERROR }, + ); + }); + }); +}; diff --git a/apps/webservice/src/app/api/azure/consent/route.ts b/apps/webservice/src/app/api/azure/consent/route.ts new file mode 100644 index 00000000..c7daead8 --- /dev/null +++ b/apps/webservice/src/app/api/azure/consent/route.ts @@ -0,0 +1,133 @@ +import type { ResourceScanEvent } from "@ctrlplane/validators/events"; +import type { NextRequest } from "next/server"; +import { NextResponse } from "next/server"; +import { Queue } from "bullmq"; +import { BAD_REQUEST, INTERNAL_SERVER_ERROR, NOT_FOUND } from "http-status"; +import IORedis from "ioredis"; +import ms from "ms"; +import { z } from "zod"; + +import { eq, takeFirstOrNull } from "@ctrlplane/db"; +import { db } from "@ctrlplane/db/client"; +import * as SCHEMA from "@ctrlplane/db/schema"; +import { Channel } from "@ctrlplane/validators/events"; + +import { env } from "~/env"; + +const redis = new IORedis(env.REDIS_URL, { maxRetriesPerRequest: null }); + +const resourceScanQueue = new Queue(Channel.ResourceScan, { + connection: redis, +}); + +const configSchema = z.object({ + workspaceId: z.string(), + tenantId: z.string(), + subscriptionId: z.string(), + name: z.string(), +}); + +export const GET = async (req: NextRequest) => { + const { searchParams } = new URL(req.url); + const state = searchParams.get("state"); + const resourceProviderId = searchParams.get("resourceProviderId"); + if (!state) + return NextResponse.json({ error: "Bad request" }, { status: BAD_REQUEST }); + + const redisKey = `azure_consent_state:${state}`; + const configJSON = await redis.get(redisKey); + if (configJSON == null) + return NextResponse.json({ error: "Bad request" }, { status: BAD_REQUEST }); + await redis.del(redisKey); + + const config = JSON.parse(configJSON); + const parsedConfig = configSchema.safeParse(config); + if (!parsedConfig.success) + return NextResponse.json({ error: "Bad request" }, { status: BAD_REQUEST }); + + const { workspaceId, tenantId, subscriptionId, name } = parsedConfig.data; + + return db.transaction(async (db) => { + const workspace = await db + .select() + .from(SCHEMA.workspace) + .where(eq(SCHEMA.workspace.id, workspaceId)) + .then(takeFirstOrNull); + + if (workspace == null) + return NextResponse.json( + { error: "Workspace not found" }, + { status: NOT_FOUND }, + ); + + const tenant = await db + .insert(SCHEMA.azureTenant) + .values({ workspaceId, tenantId }) + .returning() + .then(takeFirstOrNull); + + if (tenant == null) + return NextResponse.json( + { error: "Failed to create tenant" }, + { status: INTERNAL_SERVER_ERROR }, + ); + + const nextStepsUrl = `${env.BASE_URL}/${workspace.slug}/resource-providers/integrations/azure/${resourceProviderId}`; + + if (resourceProviderId != null) + return db + .update(SCHEMA.resourceProviderAzure) + .set({ tenantId, subscriptionId }) + .where( + eq( + SCHEMA.resourceProviderAzure.resourceProviderId, + resourceProviderId, + ), + ) + .then(() => NextResponse.redirect(nextStepsUrl)) + .catch(() => + NextResponse.json( + { error: "Failed to update resource provider" }, + { status: INTERNAL_SERVER_ERROR }, + ), + ); + + const resourceProvider = await db + .insert(SCHEMA.resourceProvider) + .values({ workspaceId, name }) + .returning() + .then(takeFirstOrNull); + + if (resourceProvider == null) + return NextResponse.json( + { error: "Failed to create resource provider" }, + { status: INTERNAL_SERVER_ERROR }, + ); + + return db + .insert(SCHEMA.resourceProviderAzure) + .values({ + resourceProviderId: resourceProvider.id, + tenantId: tenant.id, + subscriptionId, + }) + .then(() => + resourceScanQueue.add( + resourceProvider.id, + { resourceProviderId: resourceProvider.id }, + { repeat: { every: ms("10m"), immediately: true } }, + ), + ) + .then(() => + NextResponse.redirect( + `${env.BASE_URL}/${workspace.slug}/resource-providers`, + ), + ) + .catch(() => + NextResponse.json( + { error: "Failed to create resource provider" }, + { status: INTERNAL_SERVER_ERROR }, + ), + ); + }); +}; diff --git a/apps/webservice/src/env.ts b/apps/webservice/src/env.ts index 65273aee..4bce9934 100644 --- a/apps/webservice/src/env.ts +++ b/apps/webservice/src/env.ts @@ -29,6 +29,8 @@ export const env = createEnv({ OTEL_SAMPLER_RATIO: z.number().optional().default(1), OPENREPLAY_PROJECT_KEY: z.string().optional(), OPENREPLAY_INGEST_POINT: z.string().optional(), + AZURE_APP_CLIENT_ID: z.string().optional(), + REDIS_URL: z.string(), }, /** diff --git a/packages/api/src/router/resource-provider.ts b/packages/api/src/router/resource-provider.ts index 3e149ec4..9f64ed66 100644 --- a/packages/api/src/router/resource-provider.ts +++ b/packages/api/src/router/resource-provider.ts @@ -11,12 +11,14 @@ import { takeFirstOrNull, } from "@ctrlplane/db"; import { + azureTenant, createResourceProvider, createResourceProviderAws, createResourceProviderGoogle, resource, resourceProvider, resourceProviderAws, + resourceProviderAzure, resourceProviderGoogle, updateResourceProviderAws, updateResourceProviderGoogle, @@ -36,6 +38,20 @@ export const resourceProviderRouter = createTRPCRouter({ }) .input(z.string().uuid()) .query(async ({ ctx, input }) => { + const azureConfigSubquery = ctx.db + .select({ + id: resourceProviderAzure.id, + tenantId: azureTenant.tenantId, + subscriptionId: resourceProviderAzure.subscriptionId, + resourceProviderId: resourceProviderAzure.resourceProviderId, + }) + .from(resourceProviderAzure) + .innerJoin( + azureTenant, + eq(resourceProviderAzure.tenantId, azureTenant.id), + ) + .as("azureConfig"); + const providers = await ctx.db .select() .from(resourceProvider) @@ -47,6 +63,10 @@ export const resourceProviderRouter = createTRPCRouter({ resourceProviderAws, eq(resourceProviderAws.resourceProviderId, resourceProvider.id), ) + .leftJoin( + azureConfigSubquery, + eq(azureConfigSubquery.resourceProviderId, resourceProvider.id), + ) .where(eq(resourceProvider.workspaceId, input)); if (providers.length === 0) return []; @@ -92,6 +112,7 @@ export const resourceProviderRouter = createTRPCRouter({ ...provider.resource_provider, googleConfig: provider.resource_provider_google, awsConfig: provider.resource_provider_aws, + azureConfig: provider.azureConfig, resourceCount: providerCounts.find( (pc) => pc.providerId === provider.resource_provider.id, @@ -331,6 +352,27 @@ export const resourceProviderRouter = createTRPCRouter({ }); }), }), + azure: createTRPCRouter({ + byProviderId: protectedProcedure + .input(z.string().uuid()) + .meta({ + authorizationCheck: ({ canUser, input }) => + canUser + .perform(Permission.ResourceProviderGet) + .on({ type: "resourceProvider", id: input }), + }) + .query(({ ctx, input }) => + ctx.db + .select() + .from(resourceProviderAzure) + .innerJoin( + azureTenant, + eq(resourceProviderAzure.tenantId, azureTenant.id), + ) + .where(eq(resourceProviderAzure.resourceProviderId, input)) + .then(takeFirstOrNull), + ), + }), }), delete: protectedProcedure .meta({ diff --git a/packages/db/drizzle/0052_groovy_zuras.sql b/packages/db/drizzle/0052_groovy_zuras.sql new file mode 100644 index 00000000..99a5d99a --- /dev/null +++ b/packages/db/drizzle/0052_groovy_zuras.sql @@ -0,0 +1,32 @@ +CREATE TABLE IF NOT EXISTS "azure_tenant" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "workspace_id" uuid NOT NULL, + "tenant_id" text NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "resource_provider_azure" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "resource_provider_id" uuid NOT NULL, + "tenant_id" uuid NOT NULL, + "subscription_id" text NOT NULL +); +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "azure_tenant" ADD CONSTRAINT "azure_tenant_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "resource_provider_azure" ADD CONSTRAINT "resource_provider_azure_resource_provider_id_resource_provider_id_fk" FOREIGN KEY ("resource_provider_id") REFERENCES "public"."resource_provider"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "resource_provider_azure" ADD CONSTRAINT "resource_provider_azure_tenant_id_azure_tenant_id_fk" FOREIGN KEY ("tenant_id") REFERENCES "public"."azure_tenant"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "azure_tenant_tenant_id_index" ON "azure_tenant" USING btree ("tenant_id"); \ No newline at end of file diff --git a/packages/db/drizzle/meta/0052_snapshot.json b/packages/db/drizzle/meta/0052_snapshot.json new file mode 100644 index 00000000..e73248f2 --- /dev/null +++ b/packages/db/drizzle/meta/0052_snapshot.json @@ -0,0 +1,4920 @@ +{ + "id": "840eabe4-599f-4c30-b10b-2c8b94373cb0", + "prevId": "b8ea990a-52c7-4936-a32d-65196a8654fb", + "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": { + "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": false + }, + "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()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "NULL" + } + }, + "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 + }, + "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": "no action", + "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 + } + }, + "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_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_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_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.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": {} + }, + "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_organization": { + "name": "github_organization", + "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 + }, + "organization_name": { + "name": "organization_name", + "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()" + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'main'" + } + }, + "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_organization_added_by_user_id_user_id_fk": { + "name": "github_organization_added_by_user_id_user_id_fk", + "tableFrom": "github_organization", + "tableTo": "user", + "columnsFrom": [ + "added_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "github_organization_workspace_id_workspace_id_fk": { + "name": "github_organization_workspace_id_workspace_id_fk", + "tableFrom": "github_organization", + "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 + } + }, + "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": "'{}'" + }, + "deployment_id": { + "name": "deployment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "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()" + }, + "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": {} + } + }, + "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.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", + "new_resource", + "resource_changed", + "api", + "redeploy", + "force_deploy", + "new_environment", + "variable_changed", + "retry" + ] + }, + "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": [ + "completed", + "cancelled", + "skipped", + "in_progress", + "action_required", + "pending", + "failure", + "invalid_job_agent", + "invalid_integration", + "external_run_not_found" + ] + }, + "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": {} + } +} \ No newline at end of file diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json index 28a8787e..a74088d5 100644 --- a/packages/db/drizzle/meta/_journal.json +++ b/packages/db/drizzle/meta/_journal.json @@ -365,6 +365,13 @@ "when": 1736284273338, "tag": "0051_brown_gambit", "breakpoints": true + }, + { + "idx": 52, + "version": "7", + "when": 1736303059238, + "tag": "0052_groovy_zuras", + "breakpoints": true } ] -} +} \ No newline at end of file diff --git a/packages/db/src/schema/resource-provider.ts b/packages/db/src/schema/resource-provider.ts index 5b699135..40562fd0 100644 --- a/packages/db/src/schema/resource-provider.ts +++ b/packages/db/src/schema/resource-provider.ts @@ -117,3 +117,49 @@ export const resourceProviderAwsRelations = relations( }), }), ); + +export const azureTenant = pgTable( + "azure_tenant", + { + id: uuid("id").primaryKey().defaultRandom(), + workspaceId: uuid("workspace_id") + .notNull() + .references(() => workspace.id), + tenantId: text("tenant_id").notNull(), + }, + (t) => ({ uniq: uniqueIndex().on(t.tenantId) }), +); + +export const resourceProviderAzure = pgTable("resource_provider_azure", { + id: uuid("id").primaryKey().defaultRandom(), + resourceProviderId: uuid("resource_provider_id") + .notNull() + .references(() => resourceProvider.id, { onDelete: "cascade" }), + tenantId: uuid("tenant_id") + .notNull() + .references(() => azureTenant.id), + subscriptionId: text("subscription_id").notNull(), +}); + +export const createResourceProviderAzure = createInsertSchema( + resourceProviderAzure, +) + .omit({ id: true, resourceProviderId: true }) + .extend({ name: z.string().min(1) }); + +export type CreateResourceProviderAzure = z.infer< + typeof createResourceProviderAzure +>; + +export const updateResourceProviderAzure = + createResourceProviderAzure.partial(); + +export type UpdateResourceProviderAzure = z.infer< + typeof updateResourceProviderAzure +>; + +export type AzureTenant = InferSelectModel; + +export type ResourceProviderAzure = InferSelectModel< + typeof resourceProviderAzure +>; diff --git a/packages/validators/src/auth/index.ts b/packages/validators/src/auth/index.ts index a874c847..85dbb720 100644 --- a/packages/validators/src/auth/index.ts +++ b/packages/validators/src/auth/index.ts @@ -51,6 +51,7 @@ export enum Permission { ResourceProviderGet = "resourceProvider.get", ResourceProviderDelete = "resourceProvider.delete", ResourceProviderUpdate = "resourceProvider.update", + ResourceProviderCreate = "resourceProvider.create", ResourceViewCreate = "resourceView.create", ResourceViewList = "resourceView.list", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2fc0d095..fc5c35b5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -213,6 +213,12 @@ importers: '@aws-sdk/client-sts': specifier: ^3.699.0 version: 3.699.0 + '@azure/arm-containerservice': + specifier: ^21.3.0 + version: 21.3.0 + '@azure/identity': + specifier: ^4.5.0 + version: 4.5.0 '@ctrlplane/db': specifier: workspace:* version: link:../../packages/db @@ -2047,6 +2053,62 @@ packages: aws-crt: optional: true + '@azure/abort-controller@2.1.2': + resolution: {integrity: sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==} + engines: {node: '>=18.0.0'} + + '@azure/arm-containerservice@21.3.0': + resolution: {integrity: sha512-safzg6gqBoV7eYI+GW7aU4/u3b4Ca2jG9poeJ0k52mYrCJ1bBjKfTEkSfYOqs7iveSePS84pK0sIFTF+dRJF2w==} + engines: {node: '>=18.0.0'} + + '@azure/core-auth@1.9.0': + resolution: {integrity: sha512-FPwHpZywuyasDSLMqJ6fhbOK3TqUdviZNF8OqRGA4W5Ewib2lEEZ+pBsYcBa88B2NGO/SEnYPGhyBqNlE8ilSw==} + engines: {node: '>=18.0.0'} + + '@azure/core-client@1.9.2': + resolution: {integrity: sha512-kRdry/rav3fUKHl/aDLd/pDLcB+4pOFwPPTVEExuMyaI5r+JBbMWqRbCY1pn5BniDaU3lRxO9eaQ1AmSMehl/w==} + engines: {node: '>=18.0.0'} + + '@azure/core-lro@2.7.2': + resolution: {integrity: sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw==} + engines: {node: '>=18.0.0'} + + '@azure/core-paging@1.6.2': + resolution: {integrity: sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA==} + engines: {node: '>=18.0.0'} + + '@azure/core-rest-pipeline@1.18.1': + resolution: {integrity: sha512-/wS73UEDrxroUEVywEm7J0p2c+IIiVxyfigCGfsKvCxxCET4V/Hef2aURqltrXMRjNmdmt5IuOgIpl8f6xdO5A==} + engines: {node: '>=18.0.0'} + + '@azure/core-tracing@1.2.0': + resolution: {integrity: sha512-UKTiEJPkWcESPYJz3X5uKRYyOcJD+4nYph+KpfdPRnQJVrZfk0KJgdnaAWKfhsBBtAf/D58Az4AvCJEmWgIBAg==} + engines: {node: '>=18.0.0'} + + '@azure/core-util@1.11.0': + resolution: {integrity: sha512-DxOSLua+NdpWoSqULhjDyAZTXFdP/LKkqtYuxxz1SCN289zk3OG8UOpnCQAz/tygyACBtWp/BoO72ptK7msY8g==} + engines: {node: '>=18.0.0'} + + '@azure/identity@4.5.0': + resolution: {integrity: sha512-EknvVmtBuSIic47xkOqyNabAme0RYTw52BTMz8eBgU1ysTyMrD1uOoM+JdS0J/4Yfp98IBT3osqq3BfwSaNaGQ==} + engines: {node: '>=18.0.0'} + + '@azure/logger@1.1.4': + resolution: {integrity: sha512-4IXXzcCdLdlXuCG+8UKEwLA1T1NHqUfanhXYHiQTn+6sfWCZXduqbtXDGceg3Ce5QxTGo7EqmbV6Bi+aqKuClQ==} + engines: {node: '>=18.0.0'} + + '@azure/msal-browser@3.28.0': + resolution: {integrity: sha512-1c1qUF6vB52mWlyoMem4xR1gdwiQWYEQB2uhDkbAL4wVJr8WmAcXybc1Qs33y19N4BdPI8/DHI7rPE8L5jMtWw==} + engines: {node: '>=0.8.0'} + + '@azure/msal-common@14.16.0': + resolution: {integrity: sha512-1KOZj9IpcDSwpNiQNjt0jDYZpQvNZay7QAEi/5DLubay40iGYtLzya/jbjRPLyOTZhEKyL1MzPuw2HqBCjceYA==} + engines: {node: '>=0.8.0'} + + '@azure/msal-node@2.16.2': + resolution: {integrity: sha512-An7l1hEr0w1HMMh1LU+rtDtqL7/jw74ORlc9Wnh06v7TU/xpG39/Zdr1ZJu3QpjUfKJ+E0/OXMW8DRSWTlh7qQ==} + engines: {node: '>=16'} + '@babel/code-frame@7.24.7': resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} engines: {node: '>=6.9.0'} @@ -7792,6 +7854,10 @@ packages: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} + define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + define-properties@1.2.1: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} @@ -9230,6 +9296,11 @@ packages: resolution: {integrity: sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==} engines: {node: '>= 0.4'} + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + is-dotfile@1.0.3: resolution: {integrity: sha512-9YclgOGtN/f8zx0Pr4FQYMdibBiTaH3sn52vjYip4ZSf6C4/6RfTEZ+MR4GvKhCxdPh21Bg42/WL55f6KSnKpg==} engines: {node: '>=0.10.0'} @@ -9439,6 +9510,10 @@ packages: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} @@ -9628,6 +9703,10 @@ packages: resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} engines: {node: '>=0.10.0'} + jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + jsprim@1.4.2: resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==} engines: {node: '>=0.6.0'} @@ -9636,9 +9715,15 @@ packages: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} + jwa@1.4.1: + resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} + jwa@2.0.0: resolution: {integrity: sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==} + jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + jws@4.0.0: resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==} @@ -9753,15 +9838,33 @@ packages: lodash.get@4.4.2: resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + lodash.isarguments@3.1.0: resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lodash.sortby@4.7.0: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} @@ -10587,6 +10690,10 @@ packages: oniguruma-to-js@0.4.3: resolution: {integrity: sha512-X0jWUcAlxORhOqqBREgPMgnshB7ZGYszBNspP+tS9hPD3l13CdaXcHbgImoHUHlrvGx/7AvFEkTRhAGYh+jzjQ==} + open@8.4.2: + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} + engines: {node: '>=12'} + openapi-fetch@0.13.0: resolution: {integrity: sha512-6Nlf/BDbtyHwHdNrLPUiyt4CZMzL3ZyAt55yWH8W7+Z+8aYWnvca4uZHQHXViy8KcnCMqAhLM/bifh2Yjjkf6w==} @@ -12167,6 +12274,10 @@ packages: std-env@3.7.0: resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + stoppable@1.1.0: + resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==} + engines: {node: '>=4', npm: '>=6'} + stream-buffers@3.0.3: resolution: {integrity: sha512-pqMqwQCso0PBJt2PQmDO0cFj0lyqmiwOMiMSkVtRokl7e+ZTRYgDHKnuZNbqjiJXgsg4nuqtD/zxuo9KqTp0Yw==} engines: {node: '>= 0.10.0'} @@ -14323,6 +14434,108 @@ snapshots: '@smithy/types': 3.7.1 tslib: 2.8.0 + '@azure/abort-controller@2.1.2': + dependencies: + tslib: 2.8.1 + + '@azure/arm-containerservice@21.3.0': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.9.0 + '@azure/core-client': 1.9.2 + '@azure/core-lro': 2.7.2 + '@azure/core-paging': 1.6.2 + '@azure/core-rest-pipeline': 1.18.1 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/core-auth@1.9.0': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-util': 1.11.0 + tslib: 2.8.1 + + '@azure/core-client@1.9.2': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.9.0 + '@azure/core-rest-pipeline': 1.18.1 + '@azure/core-tracing': 1.2.0 + '@azure/core-util': 1.11.0 + '@azure/logger': 1.1.4 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/core-lro@2.7.2': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-util': 1.11.0 + '@azure/logger': 1.1.4 + tslib: 2.8.1 + + '@azure/core-paging@1.6.2': + dependencies: + tslib: 2.8.1 + + '@azure/core-rest-pipeline@1.18.1': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.9.0 + '@azure/core-tracing': 1.2.0 + '@azure/core-util': 1.11.0 + '@azure/logger': 1.1.4 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.5 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/core-tracing@1.2.0': + dependencies: + tslib: 2.8.1 + + '@azure/core-util@1.11.0': + dependencies: + '@azure/abort-controller': 2.1.2 + tslib: 2.8.1 + + '@azure/identity@4.5.0': + dependencies: + '@azure/abort-controller': 2.1.2 + '@azure/core-auth': 1.9.0 + '@azure/core-client': 1.9.2 + '@azure/core-rest-pipeline': 1.18.1 + '@azure/core-tracing': 1.2.0 + '@azure/core-util': 1.11.0 + '@azure/logger': 1.1.4 + '@azure/msal-browser': 3.28.0 + '@azure/msal-node': 2.16.2 + events: 3.3.0 + jws: 4.0.0 + open: 8.4.2 + stoppable: 1.1.0 + tslib: 2.8.1 + transitivePeerDependencies: + - supports-color + + '@azure/logger@1.1.4': + dependencies: + tslib: 2.8.1 + + '@azure/msal-browser@3.28.0': + dependencies: + '@azure/msal-common': 14.16.0 + + '@azure/msal-common@14.16.0': {} + + '@azure/msal-node@2.16.2': + dependencies: + '@azure/msal-common': 14.16.0 + jsonwebtoken: 9.0.2 + uuid: 8.3.2 + '@babel/code-frame@7.24.7': dependencies: '@babel/highlight': 7.24.7 @@ -20996,6 +21209,8 @@ snapshots: es-errors: 1.3.0 gopd: 1.0.1 + define-lazy-prop@2.0.0: {} + define-properties@1.2.1: dependencies: define-data-property: 1.1.4 @@ -22913,6 +23128,8 @@ snapshots: is-accessor-descriptor: 1.0.1 is-data-descriptor: 1.0.1 + is-docker@2.2.1: {} + is-dotfile@1.0.3: {} is-empty@1.2.0: {} @@ -23077,6 +23294,10 @@ snapshots: is-windows@1.0.2: {} + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + isarray@1.0.0: {} isarray@2.0.5: {} @@ -23263,6 +23484,19 @@ snapshots: jsonpointer@5.0.1: {} + jsonwebtoken@9.0.2: + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.6.3 + jsprim@1.4.2: dependencies: assert-plus: 1.0.0 @@ -23277,12 +23511,23 @@ snapshots: object.assign: 4.1.5 object.values: 1.2.0 + jwa@1.4.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + jwa@2.0.0: dependencies: buffer-equal-constant-time: 1.0.1 ecdsa-sig-formatter: 1.0.11 safe-buffer: 5.2.1 + jws@3.2.2: + dependencies: + jwa: 1.4.1 + safe-buffer: 5.2.1 + jws@4.0.0: dependencies: jwa: 2.0.0 @@ -23381,12 +23626,24 @@ snapshots: lodash.get@4.4.2: {} + lodash.includes@4.3.0: {} + lodash.isarguments@3.1.0: {} + lodash.isboolean@3.0.3: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + lodash.isplainobject@4.0.6: {} + lodash.isstring@4.0.1: {} + lodash.merge@4.6.2: {} + lodash.once@4.1.1: {} + lodash.sortby@4.7.0: {} lodash@4.17.21: {} @@ -24631,6 +24888,12 @@ snapshots: dependencies: regex: 4.3.2 + open@8.4.2: + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + openapi-fetch@0.13.0: dependencies: openapi-typescript-helpers: 0.0.15 @@ -26770,6 +27033,8 @@ snapshots: std-env@3.7.0: {} + stoppable@1.1.0: {} + stream-buffers@3.0.3: {} stream-events@1.0.5: