diff --git a/apps/jobs/package.json b/apps/jobs/package.json index be1097f91..5e930da76 100644 --- a/apps/jobs/package.json +++ b/apps/jobs/package.json @@ -19,7 +19,7 @@ "@ctrlplane/validators": "workspace:*", "cron": "^3.1.7", "lodash": "^4.17.21", - "ts-is-present": "^1.2.2", + "ts-is-present": "catalog:", "zod": "catalog:" }, "devDependencies": { diff --git a/apps/webservice/package.json b/apps/webservice/package.json index d13122cf5..db34e83b3 100644 --- a/apps/webservice/package.json +++ b/apps/webservice/package.json @@ -83,7 +83,7 @@ "superjson": "2.2.1", "swagger-ui-react": "^5.17.14", "tailwind-scrollbar": "^3.1.0", - "ts-is-present": "^1.2.2", + "ts-is-present": "catalog:", "uuid": "^10.0.0", "zod": "catalog:" }, diff --git a/integrations/terraform-cloud-scanner/package.json b/integrations/terraform-cloud-scanner/package.json index 23b281d5b..ff98c41bb 100644 --- a/integrations/terraform-cloud-scanner/package.json +++ b/integrations/terraform-cloud-scanner/package.json @@ -31,6 +31,7 @@ "handlebars": "^4.7.8", "lodash": "^4.17.21", "p-retry": "^6.2.0", + "ts-is-present": "catalog:", "zod": "catalog:" }, "devDependencies": { diff --git a/integrations/terraform-cloud-scanner/src/__tests__/scanner.test.ts b/integrations/terraform-cloud-scanner/src/__tests__/scanner.test.ts index 5a854aa16..42f6ef77e 100644 --- a/integrations/terraform-cloud-scanner/src/__tests__/scanner.test.ts +++ b/integrations/terraform-cloud-scanner/src/__tests__/scanner.test.ts @@ -25,7 +25,7 @@ vi.mock("../config.js", () => ({ CTRLPLANE_API_URL: "https://mock.ctrlplane.url", CTRLPLANE_API_KEY: "mock-api-key", CTRLPLANE_WORKSPACE_ID: "36427c59-e2bd-4b3f-bf54-54404ef6aa0e", - CTRLPLANE_WORKSPACE_TARGET_NAME: "mock-workspace-target-name", + CTRLPLANE_WORKSPACE_RESOURCE_NAME: "mock-workspace-resource-name", CTRLPLANE_SCANNER_NAME: "mock-scanner-name", CRON_ENABLED: false, CRON_TIME: "*/5 * * * *", @@ -38,7 +38,7 @@ beforeEach(() => { }); describe("Scanner Module", () => { - it("should successfully scan and register targets", async () => { + it("should successfully scan and register resources", async () => { vi.spyOn(env, "TFE_ORGANIZATION", "get").mockReturnValue("mock-org"); vi.spyOn(env, "CTRLPLANE_WORKSPACE_ID", "get").mockReturnValue( "36427c59-e2bd-4b3f-bf54-54404ef6aa0e", @@ -46,7 +46,7 @@ describe("Scanner Module", () => { vi.spyOn(env, "CTRLPLANE_SCANNER_NAME", "get").mockReturnValue( "mock-scanner", ); - vi.spyOn(env, "CTRLPLANE_WORKSPACE_TARGET_NAME", "get").mockReturnValue( + vi.spyOn(env, "CTRLPLANE_WORKSPACE_RESOURCE_NAME", "get").mockReturnValue( "{{workspace.attributes.name}}", ); @@ -119,14 +119,14 @@ describe("Scanner Module", () => { expect(listVariables).toHaveBeenCalledWith("workspace-1"); expect(patchMock).toHaveBeenCalledWith( - "/v1/target-providers/{providerId}/set", + "/v1/resource-providers/{providerId}/set", expect.objectContaining({ body: { - targets: [ + resources: [ { version: "terraform/v1", kind: "Workspace", - name: "mock-workspace-target-name", + name: "mock-workspace-resource-name", identifier: "workspace-1", config: { workspaceId: "workspace-1", @@ -158,7 +158,9 @@ describe("Scanner Module", () => { }), ); - expect(logger.info).toHaveBeenCalledWith("Successfully registered targets"); + expect(logger.info).toHaveBeenCalledWith( + "Successfully registered resources", + ); }); it("should handle scan errors gracefully", async () => { diff --git a/integrations/terraform-cloud-scanner/src/config.ts b/integrations/terraform-cloud-scanner/src/config.ts index 26ddd640c..23b3db9f1 100644 --- a/integrations/terraform-cloud-scanner/src/config.ts +++ b/integrations/terraform-cloud-scanner/src/config.ts @@ -10,7 +10,7 @@ export const env = createEnv({ CTRLPLANE_API_KEY: z.string(), CTRLPLANE_WORKSPACE_ID: z.string().uuid(), CTRLPLANE_SCANNER_NAME: z.string().default("terraform-cloud-scanner"), - CTRLPLANE_WORKSPACE_TARGET_NAME: z + CTRLPLANE_WORKSPACE_RESOURCE_NAME: z .string() .default("tfc-{{ workspace.attributes.name }}"), diff --git a/integrations/terraform-cloud-scanner/src/scanner.ts b/integrations/terraform-cloud-scanner/src/scanner.ts index 2638419e2..355a12fa9 100644 --- a/integrations/terraform-cloud-scanner/src/scanner.ts +++ b/integrations/terraform-cloud-scanner/src/scanner.ts @@ -1,5 +1,6 @@ import handlebars from "handlebars"; import _ from "lodash"; +import { isPresent } from "ts-is-present"; import { logger } from "@ctrlplane/logger"; import { ResourceProvider } from "@ctrlplane/node-sdk"; @@ -10,20 +11,16 @@ import { env } from "./config.js"; import { api } from "./sdk.js"; const workspaceTemplate = handlebars.compile( - env.CTRLPLANE_WORKSPACE_TARGET_NAME, + env.CTRLPLANE_WORKSPACE_RESOURCE_NAME, ); /** * Scans Terraform Cloud workspaces and registers them as targets with prefixed labels and a link. */ export async function scan() { - const scanner = new ResourceProvider( - { - workspaceId: env.CTRLPLANE_WORKSPACE_ID, - name: env.CTRLPLANE_SCANNER_NAME, - }, - api, - ); + const workspaceId = env.CTRLPLANE_WORKSPACE_ID; + const name = env.CTRLPLANE_SCANNER_NAME; + const scanner = new ResourceProvider({ workspaceId, name }, api); logger.info("Starting Terraform Cloud scan"); try { @@ -37,9 +34,7 @@ export async function scan() { const workspaces: Workspace[] = await listWorkspaces(); logger.info(`Found ${workspaces.length} workspaces`); - const targets = []; - - for (const workspace of workspaces) { + const resourcePromises = workspaces.map(async (workspace) => { logger.info( `Processing workspace: ${workspace.attributes.name} (ID: ${workspace.id})`, ); @@ -52,12 +47,12 @@ export async function scan() { const tagLabels = processWorkspaceTags(workspace.attributes["tag-names"]); const vcsRepoLabels = processVcsRepo(workspace.attributes["vcs-repo"]); const link = buildWorkspaceLink(workspace); - const targetName = workspaceTemplate({ workspace }); + const resourceName = workspaceTemplate({ workspace }); - const target = { + return { version: "terraform/v1", kind: "Workspace", - name: targetName, + name: resourceName, identifier: workspace.id, config: { workspaceId: workspace.id, @@ -76,15 +71,20 @@ export async function scan() { "ctrlplane/links": JSON.stringify(link), }, }; + }); - targets.push(target); - } - - logger.info(`Registering ${targets.length} unique targets`); - - await scanner.set(targets); - - logger.info("Successfully registered targets"); + const resources = await Promise.allSettled(resourcePromises).then( + (results) => + results + .map((result) => + result.status === "fulfilled" ? result.value : null, + ) + .filter(isPresent), + ); + + logger.info(`Registering ${resources.length} unique resources`); + await scanner.set(resources); + logger.info("Successfully registered resources"); } catch (error) { logger.error("An error occurred during the scan process:", error); process.exit(1); @@ -101,12 +101,10 @@ const processVariables = (variables: Variable[]) => variables .filter((variable) => variable.attributes.category === "terraform") .filter((variable) => variable.attributes.sensitive === false) - .map((variable) => { - return [ - `terraform-cloud/variables/${variable.attributes.key}`, - variable.attributes.value, - ]; - }), + .map((variable) => [ + `terraform-cloud/variables/${variable.attributes.key}`, + variable.attributes.value, + ]), ); /** @@ -128,9 +126,9 @@ const processWorkspaceTags = (tags: string[] = []) => * @param vcsRepo The VCS repository information from workspace attributes. * @returns An object containing VCS repository labels. */ -function processVcsRepo( +const processVcsRepo = ( vcsRepo?: Workspace["attributes"]["vcs-repo"], -): Record { +): Record => { if (!vcsRepo) return {}; const { identifier, branch, "repository-http-url": repoUrl } = vcsRepo; @@ -140,17 +138,15 @@ function processVcsRepo( ...(branch && { "terraform-cloud/vcs-repo/branch": branch }), ...(repoUrl && { "terraform-cloud/vcs-repo/repository-http-url": repoUrl }), }; -} +}; /** * Constructs the link to the Terraform workspace. * @param workspace The workspace object. * @returns The URL string to the workspace. */ -function buildWorkspaceLink(workspace: Workspace): Record { - return { - "Terraform Workspace": `https://app.terraform.io/app/${encodeURIComponent( - env.TFE_ORGANIZATION, - )}/workspaces/${encodeURIComponent(workspace.attributes.name)}`, - }; -} +const buildWorkspaceLink = (workspace: Workspace): Record => ({ + "Terraform Workspace": `https://app.terraform.io/app/${encodeURIComponent( + env.TFE_ORGANIZATION, + )}/workspaces/${encodeURIComponent(workspace.attributes.name)}`, +}); diff --git a/packages/job-dispatch/package.json b/packages/job-dispatch/package.json index 226d2d0d3..6de22278e 100644 --- a/packages/job-dispatch/package.json +++ b/packages/job-dispatch/package.json @@ -38,7 +38,7 @@ "murmurhash": "^2.0.1", "redis": "^4.6.15", "semver": "^7.6.2", - "ts-is-present": "^1.2.2", + "ts-is-present": "catalog:", "zod": "catalog:" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d23127d81..e3f205e86 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -42,6 +42,9 @@ catalogs: tailwindcss: specifier: ^3.4.11 version: 3.4.13 + ts-is-present: + specifier: ^1.2.2 + version: 1.2.2 tsx: specifier: ^4.19.1 version: 4.19.1 @@ -311,7 +314,7 @@ importers: specifier: ^4.17.21 version: 4.17.21 ts-is-present: - specifier: ^1.2.2 + specifier: 'catalog:' version: 1.2.2 zod: specifier: 'catalog:' @@ -643,7 +646,7 @@ importers: specifier: ^3.1.0 version: 3.1.0(tailwindcss@3.4.13(ts-node@10.9.2(@types/node@20.16.10)(typescript@5.6.3))) ts-is-present: - specifier: ^1.2.2 + specifier: 'catalog:' version: 1.2.2 uuid: specifier: ^10.0.0 @@ -904,6 +907,9 @@ importers: p-retry: specifier: ^6.2.0 version: 6.2.1 + ts-is-present: + specifier: 'catalog:' + version: 1.2.2 zod: specifier: 'catalog:' version: 3.23.8 @@ -1218,7 +1224,7 @@ importers: specifier: ^7.6.2 version: 7.6.3 ts-is-present: - specifier: ^1.2.2 + specifier: 'catalog:' version: 1.2.2 zod: specifier: 'catalog:' diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index f8671470e..f84ce7232 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -22,6 +22,7 @@ catalog: "next-auth": "5.0.0-beta.22" "@next/eslint-plugin-next": ^14.2.6 "bullmq": ^5.15.0 + "ts-is-present": ^1.2.2 catalogs: react18: